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

17

Eu tenho um arquivo que se parece com esse exemplo de brinquedo. Meu arquivo atual tem 4 milhões de linhas, das quais 10 precisam ser deletadas.

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

Eu quero excluir as linhas que se parecem com o cabeçalho, exceto pela 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?

    
por Gaius Augustus 26.01.2016 / 20:00

9 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. processe o arquivo com grep para omitir linhas que correspondam ao cabeçalho
  4. capture a saída das duas etapas acima no arquivo de saída
por 26.01.2016 / 20:06
35

Você pode usar

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

Isso excluirá as linhas com ID a partir da linha 2.

    
por 26.01.2016 / 20:05
10

Para quem não gosta de chaves

sed -e '1n' -e '/^ID/d'
  • n significa pass linha Não. 1
  • d exclui todas as linhas combinadas que começam com ^ID
por 26.01.2016 / 20:28
6

Aqui é divertido. Você pode usar sed diretamente para retirar todas as cópias da primeira linha e deixar todo o restante no lugar (incluindo a primeira linha).

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

1{h;n;} coloca a primeira linha no espaço de armazenamento, imprime e lê na próxima linha - ignorando o restante dos comandos sed da primeira linha. (Ele também ignora esse primeiro 1 teste para a segunda linha , mas isso não importa, já que o teste não teria sido aplicado na segunda linha.)

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

/^\(.*\)\n$/d exclui o conteúdo do espaço de padrão (pulando para a próxima linha) se a parte após a nova linha (ou seja, o que foi anexado do espaço de espera) corresponder exatamente à porção anterior à 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 comando G , de modo que o que é impresso é 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 P rint até a nova linha se a parte após a nova linha (ou seja, o que foi anexado do espaço de espera) < em> doesn't corresponde exatamente a parte anterior à nova linha e, em seguida, exclui incondicionalmente o espaço padrão:

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

Saída quando dada sua entrada é:

ID  Data1  Data2
1    100    100
2    100    200
3    200    100
4    100    100
5    200    200
    
por 27.01.2016 / 04:09
5

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

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

O -n sinalizador instrui o perl a efetuar 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, $.==1 só será verdadeiro para a primeira linha) como $k . O print unless $k eq $_ imprime a linha atual, se não for a mesma que foi salva em $k .

Como alternativa, a mesma coisa em awk :

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

Aqui, testamos se a linha atual é a mesma que está salva na variável x . Se o teste $0!=x for verdadeiro (se a linha atual $0 não for igual a 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 depois de verificar se a linha atual corresponde a x , isso garante que a primeira linha também seja impressa.

    
por 27.01.2016 / 13:38
4

O AWK é uma ferramenta bastante decente para esse propósito também. Aqui está uma amostra 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/ operador lógico && informa ao AWK para imprimir linha que não seja igual a 1 e não contenha ID Data1 Data2 . Observe a falta de {print} part; no awk, se uma condição de teste for avaliada como verdadeira, presume-se que a linha seja impressa.
  • | head -n 10 é apenas uma pequena adição para limitar a saída apenas às primeiras 10 linhas. Não é relevante para a parte AWK , usada apenas para fins de demonstração.

Se você quiser isso em um arquivo, redirecione a saída do comando adicionando > newFile.txt no final do comando, assim:

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

Como isso acontece? 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 lateral

O arquivo de amostra gerado foi feito com um loop de um a um milhão e imprimindo as primeiras quatro linhas do seu arquivo (então, 4 linhas vezes milhões equivale a 4 milhões de linhas), o que levou 0,09 segundos, a propósito.

awk 'BEGIN{ for(i=1;i<=1000000;i++) printf("ID  Data1  Data2\n1    100    100\n     100    200\n3    200    100\n");  }' > rmLines.txt
    
por 27.01.2016 / 07:35
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, pegue o cabeçalho e imprima-o, e a linha subseqüente DIFFERENTE daquele cabeçalho será impressa.

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

    
por 28.01.2016 / 13:41
2

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

perl -i -p -e 's/^ID.*$//s if $. > 1' file
    
por 28.01.2016 / 01:08
2

Apenas para empurrar a questão para trás um pouco ... parece que talvez sua entrada seja o resultado de ter reunido vários arquivos TSV juntos. Se você puder fazer o backup de uma etapa do pipeline de processamento (se for o proprietário ou puder falar com as pessoas que fazem isso), poderá usar uma ferramenta de reconhecimento de cabeçalho para concatenar os dados e, assim, remover o problema de ter que remover 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
    
por 16.11.2016 / 03:10