Script que compara dois arquivos, combina duas strings em qualquer lugar na linha

2

Estou tentando encontrar uma maneira eficiente de fazer algumas comparações usando um script bash. Estou usando grep e awk, mas é muito lento e não tenho uma boa maneira de separar os jogos.

Considere o arquivo de entrada1:

311 2222
833 7777
Jam 33333

e arquivo de entrada2:

A 833 0 0 0 0 7777 0 0 0 0
B no match - 2222 833 3333
C the cow jumps over the 311 moon 2222
D illicit Jam fox 33333 
E no match - Jam 
F 7777 833

Na realidade, file1 é ~ 100 linhas e file2 é ~ 10.000 linhas.

Eu quero comparar as duas cadeias no arquivo1 com cada linha no arquivo2. Se ambas as seqüências de caracteres corresponderem em qualquer lugar nessa linha, imprima a linha do arquivo2. Para o exemplo acima, idealmente a saída seria semelhante a esta (mas formatada em colunas):

C the cow jumps over the 311 moon 2222
---<separator>---
A 833 0 0 0 0 7777 0 0 0 0
F 7777 833
---<separator>--- 
D illicit Jam fox 33333 

Observe que as correspondências são agrupadas com separadores entre elas (833 7777 corresponde a duas linhas no arquivo2). É preferível que todas as correspondências sejam correspondências de palavras.

Mais uma observação: cada linha no arquivo1 corresponderá a uma ou mais linhas no arquivo2, mas cada linha no arquivo2 corresponderá a 1 ou 0 linhas no arquivo1.

[atualiza as linhas "sem correspondência" no arquivo2]

Obrigado

    
por mstream 09.03.2017 / 23:00

5 respostas

1

Esta é uma solução awk pura que funciona para dois padrões por linha (delimitada por espaço) no arquivo de padrões com a operação lógica AND:

awk 'NR==FNR{patts[$1]=$2;next}{for (i in patts) if (($0 ~ i) && ($0 ~ patts[i])) print}' patterns file

Atualizar :
Para palavras em vez de correspondência de expressões regulares, você pode avaliar esta alternativa:

awk 'NR==FNR{patts[$0]="\<" $1 "\>.*\<" $2 "\>|\<" $2 "\>.*\<" $1 "\>";next} \
{for (i in patts) if ($0 ~ patts[i]) print}' patterns file1   

Na verdade, ele está transformando o padrão 833 7777 em \<833\>.*\<7777\> | \<7777\>.*\<833\> , o que torna a correspondência de palavras e simula também o AND lógico para os dois padrões.

Esta solução é testada e não corresponde a registros como G 77771 2833

Atualização No2
Isso garantirá correspondência de palavras, operação lógica AND e impressão por grupos combinados com string separadora.

awk 'NR==FNR{patts[$0]="\<" $1 "\>.*\<" $2 "\>|\<" $2 "\>.*\<" $1 "\>";next} \
{for (i in patts) {if ($0 ~ patts[i]) !found[i]?found[i]=$0:found[i]=found[i] ORS $0}} \
END{for (k in found) {print found[k];print "-----"}}' patterns file1

#Output
A 833 0 0 0 0 7777 0 0 0 0
F 7777 833
-----
D illicit Jam fox 33333
-----                  
C the cow jumps over the 311 moon 2222
----- 

Teste on-line aqui

PS: Devido à maneira awk de trabalhar com matrizes associativas, não podemos afetar na seção END a impressão da matriz encontrada. Será de alguma forma "aleatório".

    
por 10.03.2017 / 11:57
1

Se soubermos que existem exatamente duas cadeias em cada linha de arquivo1:

while read -ra elements; do
   grep "${elements[0]}" file2 | \
     grep "${elements[1]}" && \
     echo "----"
done < file1
    
por 09.03.2017 / 23:17
1

Eu faria isso em perl porque acho que parece mais claro:

#!/usr/#bin/env perl
use strict;
use warnings;
use Data::Dumper;


my ( $pattern_file_name, $process_file_name ) = @ARGV; 

open ( my $patterns_file, '<', $pattern_file_name ) or die $!;
my @matches = map { [split] } <$patterns_file>;
close ( $patterns_file );

print "Using:\n";
print Dumper \@matches;

#my @matches = ( [ '311', '2222' ], [ '833', '7777' ], [ 'Jam', '33333' ] );

#read main file
my @results;
open  ( my $input, '<', $process_file_name ) or die $!; 

#iterate a line at a time. 
while ( my $line = <$input> ) {
 GROUP:
   for my $id ( 0 .. $#matches ) {
      #Check each set of expressions.
      foreach my $expression ( @{ $matches[$id] } ) { 
         #move to the next group if any don't match
         next GROUP unless $line =~ m/$expression/;
      }
      #didn't get skipped, so must have matched all. 
      push( @{ $results[$id] }, $line );
   }
}
print Dumper \@results;

print "\n$_\n" for @results;
close ( $input );
    
por 10.03.2017 / 15:13
0

Existe um truque que você pode fazer aqui e que funciona bem nos meus testes:

$ awk -f <(sed 's/^\|$/\//g; s/ /\/ \&\& \//g' file1) file2
#Output
A 833 0 0 0 0 7777 0 0 0 0
C the cow jumps over the 311 moon 2222
D illicit Jam fox 33333
F 7777 833

O sucesso desta solução é porque convertemos o arquivo1 (padrões) no formato awk /pattern1/ && /pattern2/{print} (a impressão é a ação padrão e pode ser omitida)

$ sed 's/^\|$/\//g; s/ /\/ \&\& \//g' file1
/311/ && /2222/
/833/ && /7777/
/Jam/ && /33333/

Em vez de escrever com sed um novo arquivo, alimentamos o sed diretamente no awk como um arquivo usando a substituição do processo.

Então o comando: awk -f <(sed 's/^\|$/\//g; s/ /\/ \&\& \//g' file1) file2 instrui o awk a ler o programa da subestação do processo e aplicá-lo ao arquivo2.

Lembre-se de que pulei os separadores, já que não está claro onde exatamente você precisa deles.

PS: Deve ser uma solução awk pura.

    
por 10.03.2017 / 02:06
0
perl -wMstrict -Mvars='*f2' -l -0777ane '
   if ( ! @ARGV ) {# this is File1 zone: slurped in $_
      while ( /^(\S+)\s+(\S+)$/mg ) {
         my $rx = qr/^(?=.*$1)(?=.*$2)/m; # AND matching of $1/$2
         pos($f2)=0;
         $f2 =~ /\G([^\n]+)/m and print $1 while $f2 =~ /$rx/mg;
         print "--- <Separator> ---" unless /\G\n\z/;
      }
   } else {# This is File2 zone: slurped whole in $f2
      $f2 = $_;
   }
' File2 File1 #<----- order is important here

Explicação:

Temos que ter em mente que a ordem de impressão é muito importante aqui. As linhas do arquivo2 são impressas da ordem determinada pelas seqüências de caracteres para ser encontrado no arquivo1. E depois que cada linha de File1 é padrão contra o File1, também precisamos de uma linha separada, independentemente de a partida ser bem-sucedida ou não. Os arquivos são slurped, File2 = > $ f2 e Arquivo é tratado como $ _ em

    
por 10.03.2017 / 04:50