compare dos columnas de archivos diferentes e imprima si coincide

16

Estoy usando Solaris 10 y, por lo tanto, las opciones grep que involucran -f no funcionan.

Tengo dos archivos separados por tuberías:

archivo1:

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

archivo 2:

abc|123|
kumar|pki|
cab|234

Me gustaría comparar las dos primeras columnas del archivo2 con el archivo1 (buscar en todo el contenido del archivo1 en las primeras dos columnas) si coinciden con imprimir la línea coincidente del archivo1. Luego busque la segunda línea del archivo 2 y así sucesivamente.

Rendimiento esperado:

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

Los archivos que tengo son enormes, contienen aproximadamente 400,000 líneas, por lo que me gustaría acelerar la ejecución.

user68365
fuente
Eliminé los espacios iniciales de sus ejemplos, si lo desea, revierta la edición. Recuerde que los espacios son significativos, solo debe tenerlos si existen en sus archivos reales.
terdon
Intente usar la versión GNU de grep, está debajo /usr/sfw/bin/ggrep. stackoverflow.com/questions/15259882/…
slm

Respuestas:

21

Para esto fue diseñado awk:

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

Explicación

  • -F'|': establece el separador de campo en |.
  • NR==FNR: NR es el número de línea de entrada actual y FNR el número de línea del archivo actual. Los dos serán iguales solo mientras se lee el primer archivo.
  • c[$1$2]++; next: si este es el primer archivo, guarde los dos primeros campos en la cmatriz. Luego, salte a la siguiente línea para que esto solo se aplique en el primer archivo.

  • c[$1$2]>0: el bloque else solo se ejecutará si este es el segundo archivo, por lo que verificamos si los campos 1 y 2 de este archivo ya se han visto ( c[$1$2]>0) y si lo han sido, imprimimos la línea. En awk, la acción predeterminada es imprimir la línea, por lo que si c[$1$2]>0es verdadera, la línea se imprimirá.


Alternativamente, ya que etiquetaste con Perl:

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

Explicación

Se abrirá la primera línea file2, lea todo hasta el segundo |( .+?\|[^|]+) y guárdelo ( $&es el resultado del último operador del partido) en el %khash.

La segunda línea procesa el archivo1, usa la misma expresión regular para extraer las dos primeras columnas e imprimir la línea si esas columnas están definidas en el %khash.


Ambos enfoques anteriores necesitarán mantener las 2 primeras columnas del archivo2 en la memoria. Eso no debería ser un problema si solo tiene unos cientos de miles de líneas, pero si lo es, podría hacer algo como

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

Pero eso será más lento.

terdon
fuente
¿Pero esto no cargará todo (las dos primeras columnas) file2en la memoria?
Joseph R.
@terdon: awk -F'|' 'NR==FNR{c[$1$2]++;next};c[$1$2] > 0'es una versión más corta.
Cuonglm
no funciona ..
user68365
@ user68365: ¿ file2Tiene filas duplicadas?
Cuonglm
NO, no tiene filas duplicadas
user68365
1

Yo creo que

grep -Ff file2 file1

es lo que estás buscando Debería ser eficiente, pero no estoy seguro de que sea tan preciso como quieras. Si abc|123(por ejemplo) se encuentra en una línea file1en diferentes columnas, esa línea también se imprimirá. Si puede garantizar que esto nunca sucederá, la línea anterior debería funcionar.

Joseph R.
fuente
Grep no sería suficiente, ya que abc | 123 puede estar presente en algún lugar del archivo. Además, estoy usando Solaris 10 y tampoco puedo usar esa opción grep.
user68365
2
@ user68365 por favor aclare todo esto en su pregunta. Debe indicarnos su sistema operativo y especificar que solo desea hacer coincidir las 2 primeras columnas.
terdon
1

Si desea pensar el problema en SQL de la misma manera, entonces definitivamente debería probar una herramienta llamada ' q ':

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

Es más claro y fácil de entender si está familiarizado con la consulta SQL.

Vincent
fuente
Gracias por una de las soluciones menos crípticas, de lejos. Eso es lo que yo quiero. Pero tuve algunos problemas para encontrar esta "herramienta q"
Rolf
Herramienta muy útil.
ghilesZ
0
$  sed 's/^/\^/' 2.txt > temp.txt ; grep 1.txt -f temp.txt
abc|123|BNY|apple|
cab|234|cyx|orange|
mr_tron
fuente
1
Como he editado y mencionado en la pregunta, las opciones grep -f no funcionan en mi sistema
user68365
Solaris 10 tiene un gnu core-utils en / usr / sfw / bin Use / usr / sfw / bin / sed y / usr / sfw / bin / grep
mr_tron