Remova linhas de cabeçalho extras do arquivo, exceto a primeira linha

18

Eu tenho um arquivo que se parece com este exemplo de brinquedo. Meu arquivo atual possui 4 milhões de linhas, das quais 10 precisam ser excluídas.

ID  Data1  Data2
1    100    100
2    100    200
3    200    100
ID  Data1  Data2
4    100    100
ID  Data1  Data2
5    200    200

Quero excluir as linhas que se parecem com o cabeçalho, exceto a primeira linha.

Arquivo final:

ID  Data1  Data2
1    100    100
2    100    200
3    200    100
4    100    100
5    200    200

Como posso fazer isso?

Gaius Augustus
fonte

Respostas:

26
header=$(head -n 1 input)
(printf "%s\n" "$header";
 grep -vFxe "$header" input
) > output
  1. pegue a linha de cabeçalho do arquivo de entrada em uma variável
  2. imprima o cabeçalho
  3. processar o arquivo greppara omitir linhas que correspondem ao cabeçalho
  4. capturar a saída das duas etapas acima no arquivo de saída
Jeff Schaller
fonte
2
ou talvez{ IFS= read -r head; printf '%s\n' "$head"; grep -vF "$head" ; } <file
iruvar
Ambas boas adições. Agradeço a don_crissti por apontar indiretamente que o posix removeu recentemente -1 da sintaxe da cabeça, em favor de -n 1.
Jeff Schaller
3
@JeffSchaller, recentemente como em 12 anos atrás. E head -1ficou obsoleto por décadas antes disso.
Stéphane Chazelas 27/01
36

Você pode usar

sed '2,${/ID/d;}'

Isso excluirá as linhas com o ID iniciando na linha 2.

bkmoney
fonte
3
legais; ou para ser mais específico com o casamento de padrões, sed '2,${/^ID Data1 Data2$/d;}' file(usando o número correto de espaços entre as colunas, é claro)
Jeff Schaller
Hum, eu pensei que você poderia omitir o ponto e vírgula por apenas 1 comando, mas ok.
precisa saber é
Não w / sane seds, não.
precisa saber é
aaaand -i para a vitória de edição no local.
precisa saber é o seguinte
4
Oused '1!{/ID/d;}'
Stéphane Chazelas
10

Para quem não gosta de colchetes

sed -e '1n' -e '/^ID/d'
  • nsignifica a passlinha não.1
  • d exclua todas as linhas correspondentes que começam com ^ID
Costas
fonte
5
Isso também pode ser reduzido para o sed '1n;/^ID/d'nome do arquivo. apenas uma sugestão
Valentin Bajrami
Observe que isso também imprimirá linhas como as IDfooque não são iguais ao cabeçalho (é improvável que faça diferença nesse caso, mas você nunca sabe).
terdon
6

Aqui está uma divertida. Você pode usar seddiretamente para retirar todas as cópias da primeira linha e deixar todo o resto no lugar (incluindo a primeira linha).

sed '1{h;n;};G;/^\(.*\)\n\1$/d;s/\n.*$//' input

1{h;n;}coloca a primeira linha no espaço de espera, imprime e lê na próxima linha - pulando o restante dos sedcomandos da primeira linha. (Ele também pula o primeiro 1teste para a segunda linha , mas isso não importa, pois esse teste não se aplicaria à segunda linha.)

G anexa uma nova linha seguida pelo conteúdo do espaço de espera no espaço do padrão.

/^\(.*\)\n\1$/dexclui o conteúdo do espaço do padrão (pulando para a próxima linha) se a parte após a nova linha (isto é, o que foi acrescentado do espaço de espera) corresponder exatamente à parte antes da nova linha. É aqui que as linhas que duplicam o cabeçalho serão excluídas.

s/\n.*$//exclui a parte do texto que foi adicionada pelo Gcomando, para que o que é impresso seja apenas a linha de texto do arquivo.

No entanto, como a regex é cara, uma abordagem um pouco mais rápida seria usar a mesma condição (negada) e Psubir para a nova linha se a parte após a nova linha (ou seja, o que foi acrescentado no espaço de espera) não corresponder exatamente à parte antes da nova linha e exclua incondicionalmente o espaço do padrão:

sed '1{h;n;};G;/^\(.*\)\n\1$/!P;d' input

Saída quando dada a sua entrada é:

ID  Data1  Data2
1    100    100
2    100    200
3    200    100
4    100    100
5    200    200
Curinga
fonte
Veja também: vi.stackexchange.com/q/6269/4676
Wildcard
@don_crissti, adição interessante; obrigado! Eu provavelmente optaria pelo mais longo, mas equivalente sed '1{h;n;};G;/^\(.*\)\n\1$/d;P;d' input; de alguma forma, é mais fácil para mim ler. :)
Wildcard
Também relacionado: unix.stackexchange.com/a/417736/135943
curinga
5

Aqui estão mais algumas opções que não exigem que você conheça a primeira linha com antecedência:

perl -ne 'print unless $_ eq $k; $k=$_ if $.==1; 

O -nsinalizador diz ao perl para fazer um loop sobre seu arquivo de entrada, salvando cada linha como $_. O $k=$_ if $.==1;salva a primeira linha ( $.é o número da linha, portanto $.==1será válido apenas para a 1ª linha) como $k. As print unless $k eq $_estampas da linha atual, se não é o mesmo que aquele salvo no $k.

Alternativamente, a mesma coisa em awk:

awk '$0!=x;(NR==1){x=$0}' file 

Aqui, testamos se a linha atual é igual à que é salva na variável x. Se o teste for $0!=xavaliado como verdadeiro (se a linha atual $0não for a mesma que x), a linha será impressa porque a ação padrão para awk em expressões verdadeiras é imprimir. A primeira linha ( NR==1) é salva como x. Como isso é feito após verificar se a linha atual corresponde x, isso garante que a primeira linha também seja impressa.

terdon
fonte
Eu gosto de não ter que conhecer a ideia de primeira linha, pois ela o torna um script generalizado para sua caixa de ferramentas.
Mark Stewart
11
esse método awk cria uma entrada de matriz vazia / falsa por linha distinta; para linhas de 4M, se todas diferentes (não claras de Q) e razoavelmente curtas (parece que sim), isso provavelmente está bom, mas se houver muito mais ou mais linhas, isso pode estragar ou morrer. !($0 in a)testes sem criar e evita isso, ou awk pode fazer a mesma lógica que você tem para perl: '$0!=x; NR==1{x=$0}'ou se a linha de cabeçalho pode estar vazio'NR==1{x=$0;print} $0!=x'
dave_thompson_085
11
@ dave_thompson_085 onde é criada uma matriz por linha? Você quer dizer !a[$0]? Por que isso criaria uma entrada a?
terdon
11
Porque é assim que o awk funciona; consulte gnu.org/software/gawk/manual/html_node/… especialmente a "NOTA".
precisa saber é o seguinte
11
@ dave_thompson_085 bem, eu vou ser amaldiçoado! Obrigado, eu não estava ciente disso. Corrigido agora.
terdon
4

O AWK também é uma ferramenta decente para esse fim. Aqui está um exemplo de execução de código:

$ awk 'NR == 1 {print} NR != 1 && $0!~/ID  Data1  Data2/' rmLines.txt | head -n 10                                
ID  Data1  Data2
1    100    100
     100    200
3    200    100
1    100    100
     100    200
3    200    100
1    100    100
     100    200
3    200    100

Divida :

  • NR == 1 {print} nos diz para imprimir a primeira linha do arquivo de texto
  • NR != 1 && $0!~/ID Data1 Data2/ O operador lógico &&diz ao AWK para imprimir uma linha que não é igual a 1 e não contém ID Data1 Data2. Observe a falta de {print}parte; em awk, se uma condição de teste for avaliada como verdadeira, é assumido que a linha será impressa.
  • | head -n 10é apenas uma pequena adição para limitar a saída apenas às 10 primeiras linhas. Não é relevante para a AWKpeça em si, apenas usado para fins de demonstração.

Se você desejar isso em um arquivo, redirecione a saída do comando anexando > newFile.txtno final do comando, da seguinte maneira:

awk 'NR == 1 {print} NR != 1 && $0!~/ID  Data1  Data2/' rmLines.txt > newFile.txt

Como ele aguenta? Muito bom, na verdade:

$ time awk 'NR == 1 {print} NR != 1 && $0!~/ID  Data1  Data2/' rmLines.txt > /dev/null                            
    0m3.60s real     0m3.53s user     0m0.06s system

Nota

O arquivo de amostra gerado foi feito para executar um loop de um a um milhão e imprimir as quatro primeiras linhas do seu arquivo (portanto, 4 linhas vezes milhões equivalem a 4 milhões de linhas), o que levou 0,09 segundos.

awk 'BEGIN{ for(i=1;i<=1000000;i++) printf("ID  Data1  Data2\n1    100    100\n     100    200\n3    200    100\n");  }' > rmLines.txt
Sergiy Kolodyazhnyy
fonte
Observe que isso também imprimirá linhas como as ID Data1 Data2 fooque não são iguais ao cabeçalho (é improvável que faça diferença nesse caso, mas você nunca sabe).
terdon
@terdon sim, exatamente certo. No entanto, o OP especificou apenas um padrão que eles querem remover e seu exemplo parece apoiar isso #
Sergiy Kolodyazhnyy
3

Awk, adaptando-se a qualquer cabeçalho automaticamente:

awk '( FNR == 1) {header=$0;print $0;}
     ( FNR > 1) && ($0 != header) { print $0;}'  file1  file2 ....

ou seja, na primeira linha, obtenha o cabeçalho e imprima-o e a linha subsequente DIFERENTE desse cabeçalho será impressa.

FNR = Número de registros no arquivo atual, para que você possa ter vários arquivos e fará o mesmo em cada um deles.

Olivier Dulac
fonte
2

Por uma questão de integridade, a solução Perl IMO um pouco mais elegante do que a @terdon forneceu:

perl -i -p -e 's/^ID.*$//s if $. > 1' file
KWubbufetowicz
fonte
11
Ah, mas o objetivo principal era evitar a necessidade de especificar o padrão e, em vez disso, lê-lo da primeira linha. Sua abordagem simplesmente excluirá qualquer linha que comece com ID. Você não tem garantia de que isso não exclua as linhas que devem ser mantidas. Desde que você trouxe elegância, não gfaz sentido se você usar ^e $. De fato, todas as suas opções m///são inúteis aqui, exceto s; eles ativam recursos que você não está usando. Assim é o $, s/^ID.*//sfaria a mesma coisa.
terdon
@terdon, é justo. O seu é muito mais universal!
KWubbufetowicz
2

Apenas para retroceder um pouco na questão ... parece que talvez sua própria entrada seja o resultado de reunir vários arquivos TSV. Se você puder fazer backup de uma etapa do seu pipeline de processamento (se você é o proprietário ou pode conversar com as pessoas que o fazem), pode usar uma ferramenta com reconhecimento de cabeçalho para concatenar os dados em primeiro lugar e, assim, remover o problema de precisar remova linhas de cabeçalho extras.

Por exemplo, usando Miller :

$ cat f1.tsv
ID  Data1 Data2
1 100 100
2 100 200
3 200 100
$ cat f2.tsv
ID  Data1 Data2
4 100 100
$ cat f3.tsv
ID  Data1 Data2
5 200 200

$ cat f1.tsv f2.tsv  f3.tsv
ID  Data1 Data2
1 100 100
2 100 200
3 200 100
ID  Data1 Data2
4 100 100
ID  Data1 Data2
5 200 200

$ mlr --tsvlite cat f1.tsv f2.tsv  f3.tsv
ID  Data1 Data2
1 100 100
2 100 200
3 200 100
4 100 100
5 200 200
John Kerl
fonte
11
Obrigado por adicionar este petisco. Isso será extremamente útil no futuro, pois a maioria dos meus pipelines exige a junção e mesclagem de arquivos de amostras individuais.
Gaius Augustus