Solo muestra las líneas que están en todos los files de text al less una vez

a.txt

cat a.txt a b x c 

b.txt

 cat b.txt d e a f 

La Q:

 SOMEMAGICK *.txt a 

P: ¿cómo puedo mostrar solo las líneas que están en todos los files * .txt?

Qué tal si

 cat *.txt | sort | uniq -c | egrep "^ +$(ls -1 *.txt | wc -l) " 

Y luego para eliminar el número de apariciones, podrías agregar …

 cat *.txt | sort | uniq -c | egrep "^ +$(ls -1 *.txt | wc -l) " | sed -re 's/^ +[0-9]+ //' 

Según el comentario de @Stephane, lo anterior no funcionará si una línea aparece varias veces en un solo file. Aquí clasifico y uniq cada file primero para evitar eso:

 for f in *.txt; do sort -u $f > $f.uniqd; done cat *.uniqd | sort | uniq -c | egrep "^ +$(ls -1 *.uniqd | wc -l) " | sed -re 's/^ +[0-9]+ //' 

Aunque ahora ya no es de una sola línea. 🙂

 awk 'FNR == 1 { FILENUM++ } SEEN[$0] == FILENUM - 1 { SEEN[$0] = FILENUM } END { for (s in SEEN) if (FILENUM == SEEN[s]) print s }' *.txt 

Explicación

Al leer la primera línea de cada file, incremente FILENUM , de modo que cuando lea el n ° file, FILENUM sea n .

Al leer cada línea, cuente la cantidad de files en los que se ha visto esa línea (pero solo tiene que molestarse en hacerlo si la línea se ha visto en todos los files anteriores).

Cuando no haya más información para leer, imprima todas las líneas que se han visto en todos los files.

Precaución: al igual que con muchas de las soluciones publicadas aquí, esta también tiene una debilidad. De acuerdo con la pregunta, si alguno de los files de input está vacío, se supone que no habrá ningún resultado . Sin embargo, dado que awk es una herramienta orientada a líneas, ignora los files vacíos. Es decir, el FNR == 1 { FILENUM++ } no puede FILENUM para files vacíos.

Con GNU awk, es posible solucionar este error utilizando la ARGIND incorporada ARGIND .

 gawk 'SEEN[$0] == ARGIND - 1 { SEEN[$0] = ARGIND } END { for (s in SEEN) if (ARGIND == SEEN[s]) print s }' *.txt 

Usando GNU awk

 awk '{ x[$0][FILENAME] } END{ num_files=ARGC-1; for (b in x) if (length(x[b]) == num_files) print b }' a.txt b.txt c.txt 

Podrías hacerlo:

 export LC_ALL=C sort -u a.txt | comm -12 - <(sort -u b.txt) | comm -12 - <(sort -u c.txt) | comm -12 - <(sort -u d.txt) 

Lo cual sería relativamente eficiente, pero no es fácil extenderlo a una cantidad arbitraria de files.

Me gusta una solución más fácil usando join :

 join <(sort a.txt) <(sort b.txt) 

Esto funciona en sus dos files de input, pero puede que no se comporte como espera en las líneas que contienen espacios, sino que también generará líneas duplicadas varias veces.

Para remediar el segundo problema, solo

 join <(sort a.txt) <(sort b.txt) | uniq 

El primero es un poco más complicado, pero hice trampa un poco con el indicador -t , para usar un carácter no existente como separador de campo:

 $ cat a.txt This test foo bar does work $ cat b.txt This is a test foo does not work does work $ join <(sort a.txt) <(sort b.txt) | uniq does work work foo bar does not work This test is a test $ join -t : <(sort a.txt) <(sort b.txt) | uniq does work 

Para 2 files

Esto no es más complicado que usar la habilidad de grep para usar una list de palabras. Por ejemplo:

 $ grep -f b.txt a.txt 

Ejemplo

 # a.txt $ cat a.txt a abc defg de bcd xyz bcd c # b.txt $ cat b.txt d e bcd a f bcd # common lines to a.txt & b.txt $ grep -Fxf b.txt a.txt a bcd 

NOTA: Según los datos, es posible que deba agregar un | sort -u | sort -u después de grep si hay líneas duplicadas en los files!

Detalles

 -F, --fixed-strings Interpret PATTERN as a list of fixed strings, separated by newlines, any of which is to be matched. (-F is specified by POSIX.) -x, --line-regexp Select only those matches that exactly match the whole line. (-x is specified by POSIX.) -f FILE, --file=FILE Obtain patterns from FILE, one per line. The empty file contains zero patterns, and therefore matches nothing. (-f is specified by POSIX.) 

Para 3 o más

Puede utilizar el hecho de que si compara un file con los demás, lo que es común en todos ellos en comparación con este file, todos los files deben compartir esta línea común. De nuevo usando grep -f como arriba pero esta vez tendremos que recorrer los files usando un ciclo for .

 $ mf=""; for i in *.txt; do [ -z "$mf" ] && mf=$i && continue; grep -Fxf $mf $i;done | sort -u 

Si agregamos algunos files adicionales a la mezcla:

 # c.txt $ cat c.txt a z d bcd e q bcd # d.txt $ cat d.txt a z e z bcd bcd 

Ejecutar nuestro código produce esto:

 $ mf=""; for i in *.txt;do [ -z "$mf" ] && mf=$i && continue; grep -Fxf $mf $i;done | sort -u a bcd