compare duas colunas de arquivos diferentes e imprima se corresponder

16

Estou usando o Solaris 10 e, portanto, as opções grep envolvendo -f não funcionam.

Eu tenho dois arquivos separados por canal:

file1:

abc|123|BNY|apple|
cab|234|cyx|orange|
def|kumar|pki|bird|

arquivo 2:

abc|123|
kumar|pki|
cab|234

Gostaria de comparar as duas primeiras colunas do arquivo2 com o arquivo1 (pesquise todo o conteúdo do arquivo1 nas duas primeiras colunas) se elas corresponderem à impressão da linha correspondente do arquivo1. Em seguida, procure a segunda linha do arquivo 2 e assim por diante.

Saída esperada:

abc|123|BNY|apple|
cab|234|cyx|orange|

Os arquivos que eu tenho são enormes, contendo cerca de 400.000 linhas, então eu gostaria de acelerar a execução.

user68365
fonte
Eu removi os espaços iniciais dos seus exemplos; se você quiser, role a edição de volta. Lembre-se de que os espaços são significativos, você só deve tê-los se eles existirem em seus arquivos reais.
terdon
Tente usar a versão GNU de grep, está abaixo /usr/sfw/bin/ggrep. stackoverflow.com/questions/15259882/…
slm

Respostas:

21

É para isso que o awk foi projetado:

$ awk -F'|' 'NR==FNR{c[$1$2]++;next};c[$1$2] > 0' file2 file1
abc|123|BNY|apple|
cab|234|cyx|orange|

Explicação

  • -F'|': define o separador de campo para |.
  • NR==FNR: NR é o número da linha de entrada atual e FNR o número da linha do arquivo atual. Os dois serão iguais apenas enquanto o 1º arquivo estiver sendo lido.
  • c[$1$2]++; next: se esse for o 1º arquivo, salve os dois primeiros campos na cmatriz. Em seguida, pule para a próxima linha para que isso seja aplicado apenas no 1º arquivo.

  • c[$1$2]>0: o bloco else será executado somente se este for o segundo arquivo, portanto verificamos se os campos 1 e 2 deste arquivo já foram vistos ( c[$1$2]>0) e, se foram, imprimimos a linha. Em awk, a ação padrão é imprimir a linha e, se c[$1$2]>0for verdadeira, a linha será impressa.


Como alternativa, desde que você marcou com Perl:

perl -e 'open(A, "file2"); while(<A>){/.+?\|[^|]+/ && $k{$&}++};
         while(<>){/.+?\|[^|]+/ && do{print if defined($k{$&})}}' file1

Explicação

A primeira linha será aberta file2, leia tudo até a segunda |( .+?\|[^|]+) e salve ( $&o resultado do último operador de correspondência) no %khash.

A segunda linha processa o arquivo1, usa o mesmo regex para extrair as primeiras duas colunas e imprimir a linha se essas colunas estiverem definidas no %khash.


Ambas as abordagens acima precisarão conter as 2 primeiras colunas do arquivo2 na memória. Isso não deve ser um problema se você tiver apenas algumas centenas de milhares de linhas, mas se for, você poderia fazer algo como

cut -d'|' -f 1,2 file2 | while read pat; do grep "^$pat" file1; done

Mas isso será mais lento.

terdon
fonte
Mas isso não carregará tudo (as duas primeiras colunas) file2na memória?
Joseph R.
@terdon: awk -F'|' 'NR==FNR{c[$1$2]++;next};c[$1$2] > 0'é a versão mais curta.
cuonglm
não funciona ..
user68365 06/06
@ user68365: Tem file2linhas duplicadas?
cuonglm
NÃO, ele não possui nenhuma linha duplicada #
68686
1

eu acho que

grep -Ff file2 file1

é o que você está procurando. Deve ser eficiente, mas não tenho certeza de que será tão preciso quanto você deseja. Se abc|123(por exemplo) for encontrado em uma linha em file1colunas diferentes, essa linha também será impressa. Se você pode garantir que isso nunca aconteça, a linha acima deve funcionar.

Joseph R.
fonte
O Grep não seria suficiente, pois o abc | 123 pode estar presente em algum lugar no arquivo. Além disso, estou usando o Solaris 10 e também não consigo usar essa opção grep.
user68365
2
@ user68365, esclareça tudo isso em sua pergunta. Você precisa nos informar seu sistema operacional e especificar que deseja corresponder apenas às duas primeiras colunas.
terdon
1

Se você gostaria de pensar o problema no SQL da mesma maneira, definitivamente deveria tentar uma ferramenta chamada ' q ':

$ q -d '|' "select f1.* from file1 f1 join file2 f2 on (f1.c1 = f2.c1 and f1.c2 = f2.c2)"

É mais claro e fácil de entender se você estiver familiarizado com a consulta SQL.

Vincent
fonte
Obrigado por uma das soluções menos enigmáticas, de longe. Isso é o que eu quero. Mas eu tinha algumas encontrar esta "ferramenta q"
Rolf
Ferramenta muito útil
precisa saber é o seguinte
0
$  sed 's/^/\^/' 2.txt > temp.txt ; grep 1.txt -f temp.txt
abc|123|BNY|apple|
cab|234|cyx|orange|
mr_tron
fonte
11
Como tenho editado e mencionado na pergunta, as opções do grep -f não funciona no meu sistema
user68365
Solaris 10 tem um núcleo-utils em / usr / sfw / bin GNU Uso / usr / sfw / bin / sed e / usr / sfw / bin / grep
mr_tron