Comando Unix para encontrar líneas comunes en dos archivos


179

Estoy seguro de que una vez encontré un comando Unix que podía imprimir las líneas comunes de dos o más archivos, ¿alguien sabe su nombre? Fue mucho más simple que diff.


55
Las respuestas a esta pregunta no son necesariamente lo que todos querrán, ya que commrequiere archivos de entrada ordenados. Si quieres solo línea por línea común, es genial. Pero si quieres lo que yo llamaría "anti-diff", commno hace el trabajo.
Robert P. Goldman

@ RobertP.Goldman hay una manera de volverse común entre dos archivos cuando el archivo1 contiene un patrón parcial como pr-123-xy-45y el archivo2 contiene ec11_orop_pr-123-xy-45.gz. Necesito el archivo 3 que contieneec11_orop_pr-123-xy-45.gz
Chandan Choudhury

Vea esto para ordenar los archivos de texto línea por línea
y2k-shubham el

Respuestas:


216

El comando que estás buscando es comm. p.ej:-

comm -12 1.sorted.txt 2.sorted.txt

Aquí:

-1 : suprime la columna 1 (líneas exclusivas de 1.sorted.txt)

-2 : suprime la columna 2 (líneas exclusivas de 2.sorted.txt)


27
Uso típico: comm -12 1.sorted.txt 2.sorted.txt
Fedir RYKHTIK

45
Si bien la comunicación necesita archivos ordenados, puede tomar grep -f file1 file2 para obtener las líneas comunes de ambos archivos.
ferdy

2
@ferdy (Repetir mi comentario de su respuesta, ya que la suya es esencialmente una respuesta repetida publicada como un comentario) grephace algunas cosas raras que no puede esperar. Específicamente, todo en 1.txtse interpretará como una expresión regular y no como una cadena simple. Además, cualquier línea en blanco 1.txtcoincidirá con todas las líneas 2.txt. Por greplo tanto , solo funcionará en situaciones muy específicas. Al menos te gustaría usar fgrep(ogrep -f ) pero lo de la línea en blanco probablemente causará estragos en este proceso.
Christopher Schultz

11
Vea la respuesta de ferdy a continuación, y los comentarios de Christopher Schultz y mis comentarios al respecto. TL; DR - uso . grep -F -x -f file1 file2
Jonathan Leffler

1
@bapors: proporcioné un Q&A con respuesta propia como ¿Cómo obtener la salida del commcomando en 3 archivos separados? La respuesta fue demasiado grande para caber cómodamente aquí.
Jonathan Leffler

62

Para aplicar fácilmente el comando comm a archivos sin clasificar , use la sustitución de procesos de Bash :

$ bash --version
GNU bash, version 3.2.51(1)-release
Copyright (C) 2007 Free Software Foundation, Inc.
$ cat > abc
123
567
132
$ cat > def
132
777
321

Por lo tanto, los archivos abc y def tienen una línea en común, la que tiene "132". Usando comm en archivos sin clasificar:

$ comm abc def
123
    132
567
132
    777
    321
$ comm -12 abc def # No output! The common line is not found
$

La última línea no produjo salida, la línea común no fue descubierta.

Ahora usa comm en archivos ordenados, ordenando los archivos con sustitución de proceso:

$ comm <( sort abc ) <( sort def )
123
            132
    321
567
    777
$ comm -12 <( sort abc ) <( sort def )
132

¡Ahora tenemos la línea 132!


2
así que ... sort abc > abc.sorted, sort dev > def.sortedy luego comm -12 abc.sorted def.sorted?
Nikana Reklawyks

1
@NikanaReklawyks Y luego recuerde eliminar los archivos temporales después y hacer frente a la limpieza en caso de error. En muchos escenarios, la sustitución del proceso también será mucho más rápida porque puede evitar la E / S del disco siempre que los resultados se ajusten a la memoria.
tripleee

29

Para complementar el Perl one-liner, aquí está su awkequivalente:

awk 'NR==FNR{arr[$0];next} $0 in arr' file1 file2

Esto leerá todas las líneas de file1la matriz arr[]y luego comprobará si cada línea file2ya existe dentro de la matriz (es decir file1). Las líneas que se encuentran se imprimirán en el orden en que aparecen file2. Tenga en cuenta que la comparación in arrutiliza la línea completa desde el file2índice hasta la matriz, por lo que solo informará coincidencias exactas en líneas completas.


2
Esta es la respuesta correcta. Ninguno de los otros puede ser hecho para trabajar en general (no he probado perllos, porque). Un millón de gracias, Sra
Entonio

1
Preservar el orden cuando se muestran las líneas comunes puede ser realmente útil en algunos casos que excluirían la comunicación debido a eso.
tuxayo

1
En caso de que alguien quiera hacer lo mismo en función de una determinada columna pero no sepa awk, simplemente reemplace ambos $ 0 por $ 5, por ejemplo para la columna 5, de modo que obtenga líneas compartidas en 2 archivos con las mismas palabras en la columna 5
FatihSarigol

24

Tal vez te refieres comm?

Compare los archivos ordenados FILE1 y FILE2 línea por línea.

Sin opciones, produce una salida de tres columnas. La columna uno contiene líneas exclusivas de FILE1, la columna dos contiene líneas exclusivas de FILE2 y la columna tres contiene líneas comunes a ambos archivos.

El secreto para encontrar esta información son las páginas de información. Para los programas GNU, son mucho más detallados que sus páginas de manual. Pruebe info coreutilsy le mostrará todas las pequeñas utilidades útiles.


19

Mientras

grep -v -f 1.txt 2.txt > 3.txt

le ofrece las diferencias de dos archivos (lo que está en 2.txt y no en 1.txt), podría hacer fácilmente un

grep -f 1.txt 2.txt > 3.txt

para recopilar todas las líneas comunes, lo que debería proporcionar una solución fácil a su problema. Si ha ordenado archivos, no commobstante , debe tomarlos . ¡Saludos!


2
grephace algunas cosas raras que no podrías esperar. Específicamente, todo en 1.txtse interpretará como una expresión regular y no como una cadena simple. Además, cualquier línea en blanco 1.txtcoincidirá con todas las líneas 2.txt. Entonces esto solo funcionará en situaciones muy específicas.
Christopher Schultz

13
@ChristopherSchultz: es posible actualizar esta respuesta para que funcione mejor usando grepanotaciones POSIX , que son compatibles con las grepvariantes más modernas de Unix. Agregue -F(o use fgrep) para suprimir expresiones regulares. Agregue -x(para exacto) para que coincida solo con líneas completas.
Jonathan Leffler

¿Por qué debemos tomar commpara archivos ordenados?
Ulysse BN

2
@UlysseBN commpuede trabajar con archivos arbitrariamente grandes siempre que estén ordenados porque solo necesita mantener tres líneas en la memoria (supongo que GNU commincluso sabría mantener solo un prefijo si las líneas son realmente largas). La grepsolución necesita mantener todas las expresiones de búsqueda en la memoria.
tripleee

9

Si los dos archivos aún no están ordenados, puede usar:

comm -12 <(sort a.txt) <(sort b.txt)

y funcionará, evitando el mensaje de error comm: file 2 is not in sorted order al hacerlo comm -12 a.txt b.txt.


Tienes razón, pero esto es esencialmente repetir otra respuesta , que realmente no proporciona ningún beneficio. Si decide responder una pregunta anterior que tiene respuestas bien establecidas y correctas, agregar una nueva respuesta al final del día puede no darle ningún crédito. Si tiene alguna información nueva distintiva, o si está convencido de que las otras respuestas están todas equivocadas, agregue una nueva respuesta, pero 'otra respuesta' que proporciona la misma información básica mucho tiempo después de que se formuló la pregunta, generalmente gana ' No te ganaré mucho crédito.
Jonathan Leffler

Ni siquiera vi esta respuesta @JonathanLeffler porque esta parte estaba al final de la respuesta, mezclada con otros elementos de respuesta antes. Si bien la otra respuesta es más precisa, creo que el beneficio mío es que para alguien que quiere una solución rápida solo tendrá 2 líneas para leer. A veces estamos buscando respuestas detalladas y a veces tenemos prisa y una respuesta rápida de leer lista para pegar está bien.
Basj

Además, no me importa el crédito / representante, no publiqué para este propósito.
Basj

1
Observe también que la sintaxis de sustitución del proceso <(command)no es portátil para el shell POSIX, aunque funciona en Bash y algunos otros.
tripleee

8
perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/'  file1 file2

esto funciona mejor que el commcomando, ya que busca en cada línea de file1in file2donde commsolo se comparará si line nin file1es igual a line nin file2.
teriiehina

1
@teriiehina: No; commno compara simplemente la línea N en el archivo 1 con la línea N en el archivo 2. Puede gestionar perfectamente una serie de líneas insertadas en cualquier archivo (lo que es equivalente a eliminar una serie de líneas del otro archivo, por supuesto). Simplemente requiere que las entradas estén ordenadas.
Jonathan Leffler

Mejor que las commrespuestas si uno quiere mantener el orden. Mejor que awkresponder si uno no quiere duplicados.
tuxayo



3

En una versión limitada de Linux (como un QNAP (nas) en el que estaba trabajando):

  • la comunicación no existía
  • grep -f file1 file2puede causar algunos problemas como dijo @ChristopherSchultz y el uso grep -F -f file1 file2fue realmente lento (más de 5 minutos, no lo terminé, más de 2-3 segundos con el siguiente método en archivos de más de 20 MB)

Entonces, esto es lo que hice:

sort file1 > file1.sorted
sort file2 > file2.sorted

diff file1.sorted file2.sorted | grep "<" | sed 's/^< *//' > files.diff
diff file1.sorted files.diff | grep "<" | sed 's/^< *//' > files.same.sorted

Si files.same.sorteddebe haber estado en el mismo orden que los originales, agregue esta línea para el mismo orden que el archivo1:

awk 'FNR==NR {a[$0]=$0; next}; $0 in a {print a[$0]}' files.same.sorted file1 > files.same

o, para el mismo orden que el archivo2:

awk 'FNR==NR {a[$0]=$0; next}; $0 in a {print a[$0]}' files.same.sorted file2 > files.same

2

Solo como referencia si alguien todavía está buscando cómo hacer esto para varios archivos, vea la respuesta vinculada a Buscar líneas coincidentes en muchos archivos.


Combinando estas dos respuestas ( ans1 y ans2 ), creo que puede obtener el resultado que necesita sin ordenar los archivos:

#!/bin/bash
ans="matching_lines"

for file1 in *
do 
    for file2 in *
        do 
            if  [ "$file1" != "$ans" ] && [ "$file2" != "$ans" ] && [ "$file1" != "$file2" ] ; then
                echo "Comparing: $file1 $file2 ..." >> $ans
                perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/' $file1 $file2 >> $ans
            fi
         done 
done

Simplemente guárdelo, dele derechos de ejecución ( chmod +x compareFiles.sh) y ejecútelo. Tomará todos los archivos presentes en el directorio de trabajo actual y hará una comparación de todos contra todos dejando en el archivo "matching_lines" el resultado.

Cosas a mejorar:

  • Saltar directorios
  • Evite comparar todos los archivos dos veces (file1 vs file2 y file2 vs file1).
  • Tal vez agregue el número de línea al lado de la cadena correspondiente

-2
rm file3.txt

cat file1.out | while read line1
do
        cat file2.out | while read line2
        do
                if [[ $line1 == $line2 ]]; then
                        echo $line1 >>file3.out
                fi
        done
done

Esto debería hacerlo.


1
Probablemente deberías usar rm -f file3.txtsi vas a eliminar el archivo; eso no informará ningún error si el archivo no existe. OTOH, no sería necesario si su secuencia de comandos simplemente se hizo eco de la salida estándar, permitiendo que el usuario de la secuencia de comandos elija a dónde debe ir la salida. En última instancia, es probable que desee utilizar $1y $2(argumentos de línea de comando) en lugar de nombres de archivo fijos ( file1.outy file2.out). Eso deja el algoritmo: va a ser lento. Se leerá file2.outuna vez para cada línea file1.out. Será lento si los archivos son grandes (digamos varios kilobytes).
Jonathan Leffler

Si bien esto puede funcionar nominalmente si tiene entradas que no contienen metacaracteres de shell (pista: vea qué advertencias obtiene de shellcheck.net ), este enfoque ingenuo es terriblemente ineficiente. Una herramienta como la grep -Fque lee un archivo en la memoria y luego hace un solo paso sobre el otro evita que se repita repetidamente sobre ambos archivos de entrada.
tripleee
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.