Intersección de dos listas en Bash

163

Estoy tratando de escribir un script simple que enumere los contenidos encontrados en dos listas. Para simplificar, usemos ls como ejemplo. Imagina que "uno" y "dos" son directorios.

one = `ls one`
dos = `ls dos`
intersección $ uno $ dos

Todavía estoy bastante verde en bash, así que siéntete libre de corregir cómo estoy haciendo esto. Solo necesito algún comando que imprima todos los archivos en "uno" y "dos". Deben existir en ambos. Puede llamar a esto la "intersección" entre "uno" y "dos".

Usuario1
fuente
Nada aquí realmente responde a la pregunta: cómo intersecar dos variables en un script Bash.
jameshfisher
Parece una nueva pregunta en mi opinión, esa pregunta está claramente respondida aquí.
Jean-Christophe Meillaud
Un enfoque posiblemente más útil se encuentra en el stackoverflow
tripleee

Respuestas:

285
comm -12  <(ls 1) <(ls 2)
ghostdog74
fuente
37
No puedo creer que no tuviera conocimiento commhasta hoy. Esto solo hizo toda mi semana :)
Darragh Enright
22
commrequiere que las entradas estén ordenadas. En este caso, lsordena automáticamente su salida, pero otros usos pueden necesitar hacer esto:comm -12 <(some-command | sort) <(some-other-command | sort)
Alexander Bird
11
NO USE la salida de ls para nada. ls es una herramienta para mirar interactivamente los metadatos del directorio. Cualquier intento de analizar la salida de ls con código está roto. Los globos son mucho más simples Y correctos: '' para el archivo en * .txt ''. Lea mywiki.wooledge.org/ParsingLs
Rany Albeg Wein el
2
¡Acabo de usar esto en un esfuerzo por encontrar usos de un publicmétodo error()proporcionado por un rasgo, en combinación con git grep, y fue increíble! Corrí $ comm -12 <(git grep -il "\$this->error(" -- "*.php") <(git grep -il "Dash_Api_Json_Response" -- "*.php")y, afortunadamente, terminé con el nombre del archivo que contenía el rasgo.
localheinz
3
Esto es muy gracioso. Estaba tratando de hacer algunas cosas locas con awk.
Rolf
55

Solución con comm

commes genial pero de hecho necesita trabajar con una lista ordenada. Y afortunadamente aquí usamos lscuál de la lspágina de manual de Bash

Ordene las entradas alfabéticamente si ninguna de -cftuSUX ni --sort.

comm -12  <(ls one) <(ls two)

Alternativa con sort

Intersección de dos listas:

sort <(ls one) <(ls two) | uniq -d

diferencia simétrica de dos listas:

sort <(ls one) <(ls two) | uniq -u

Prima

Juega con ello ;)

cd $(mktemp -d) && mkdir {one,two} && touch {one,two}/file_{1,2}{0..9} && touch two/file_3{0..9}
Jean-Christophe Meillaud
fuente
2
En lugar de complemento , creo que eso es lo que generalmente se llama diferencia simétrica .
Andrew Lazarus
29

Usa el commcomando:

ls one | sort > /tmp/one_list
ls two | sort > /tmp/two_list
comm -12 /tmp/one_list /tmp/two_list

"sort" no es realmente necesario pero siempre lo incluyo antes de usar "comm" por si acaso.

DVK
fuente
55
Es bueno incluirlo ya que necesita ser ordenado, y él solo usó ls como ejemplo.
Thor84no
3

Una alternativa menos eficiente (que la comunicación):

cat <(ls 1 | sort -u) <(ls 2 | sort -u) | uniq -d
Benubird
fuente
1
Si está usando / bin / tablero de Debian o alguna otra shell no Bash en las secuencias de comandos, puede salida de los comandos de la cadena usando paréntesis: (ls 1; ls 2) | sort -u | uniq -d.
nitrógeno
1
@ MikaëlMayer Debe marcar el nombre de la persona a la que responde, de lo contrario se supone que se refiere a mí.
Benubird
@nitrogen MikaëlMayer está en lo correcto: el chainging sort -u | uniq -dno hace nada, porque el tipo ha eliminado los duplicados antes de que uniq comience a buscarlos. Creo que no has entendido lo que está haciendo mi comando.
Benubird
@Benubird Tampoco pude obtener tu comando cat <(ls 1 | sort -u) <(ls 2 | sort -u) | uniq -dpara emitir nada. Mi comando debería leer (ls 1; ls 2) | sort | uniq -d, sin el -u, para mostrar la intersección de la lista. @ MikaëlMayer tenía razón en que mi comando original estaba roto.
nitrógeno
@nitrogen La razón por la que estoy usando cat es porque quiero que sea una solución generalizable, para que pueda reemplazarla lscon otra cosa, por ejemplo find. Su solución no permite esto, porque si uno de los comandos devuelve dos líneas iguales, lo recoge como un duplicado. El mío funciona incluso si el usuario quiere hacer ls 1/*y comparar todos los archivos en subdirectorios. De lo contrario, sí, también funciona. Es posible que el mío sea específico de bash.
Benubird
2

Unir es otra buena opción dependiendo de la entrada y la salida deseada

join -j1 -a1 <(ls 1) <(ls 2)
frogstarr78
fuente
-1

Hay otra pregunta de Stackoverflow "Intersección de matriz en bash", que está marcada como un duplicado de esto. En mi opinión, no es exactamente lo mismo, ya que esa pregunta habla sobre la comparación de dos matrices bash, mientras que esta pregunta se centra en los archivos bash. Una respuesta de una línea a la otra pregunta, que ahora está cerrada, es la siguiente:

# List1=( 0 1 2 3 4   6 7 8 9 10 11 12)
# List2=(   1 2 3   5 6   8 9    11 )
# List3=($(comm -12 <(echo ${List1[*]}| tr " " "\n"| sort) <(echo ${List2[*]} | tr " " "\n"| sort)| sort -g))
# echo ${List3[*]}
1 2 3 6 8 9 11

La utilidad de comunicación realiza una ordenación alfanumérica, mientras que las respuestas "Array intersection in bash" usan números; de ahí el uso de "sort" y "sort -g".

Chuck Newman
fuente