Intersection de deux listes dans Bash


163

J'essaye d'écrire un script simple qui listera le contenu trouvé dans deux listes. Pour simplifier, utilisons ls comme exemple. Imaginez "un" et "deux" sont des répertoires.

one = `ls one`
deux = `ls deux`
intersection un $ deux $

Je suis encore assez vert en bash, alors n'hésitez pas à corriger ma façon de faire. J'ai juste besoin d'une commande qui imprimera tous les fichiers en "un" et "deux". Ils doivent exister dans les deux. Vous pourriez appeler cela «l'intersection» entre «un» et «deux».


Rien ici ne répond réellement à la question: comment croiser deux variables dans un script Bash.
jameshfisher

Cela semble être une nouvelle question à mon avis, cette question est clairement répondue ici.
Jean-Christophe Meillaud

Une approche sans doute plus utile est dans le stackoverflow.com/questions/2312762/…
presque en

Réponses:


285
comm -12  <(ls 1) <(ls 2)

37
Je ne peux pas croire que je n'avais aucune connaissance commjusqu'à aujourd'hui Cela vient de faire toute ma semaine :)
Darragh Enright

22
commexige que les entrées soient triées. Dans ce cas, lstrie automatiquement sa sortie, mais d'autres utilisations peuvent avoir besoin de le faire:comm -12 <(some-command | sort) <(some-other-command | sort)
Alexander Bird

11
N'UTILISEZ PAS la sortie de ls pour quoi que ce soit. ls est un outil permettant de consulter de manière interactive les métadonnées des répertoires. Toute tentative d'analyse de la sortie de ls avec du code est interrompue. Les globes sont beaucoup plus simples ET corrects: '' pour fichier dans * .txt ''. Lire mywiki.wooledge.org/ParsingLs
Rany Albeg Wein

2
Je viens de l'utiliser dans le but de trouver des utilisations d'une publicméthode error()fournie par un trait, en combinaison avec git grep, et c'était génial! J'ai couru $ comm -12 <(git grep -il "\$this->error(" -- "*.php") <(git grep -il "Dash_Api_Json_Response" -- "*.php")et, heureusement, je me suis retrouvé avec le nom du fichier qui contenait le trait.
localheinz

3
C'est hilarant. J'essayais de faire des trucs dingues avec awk.
Rolf

55

Solution avec comm

commest génial mais en effet besoin de travailler avec une liste triée. Et heureusement ici, nous utilisons lslequel de la lspage de manuel Bash

Triez les entrées par ordre alphabétique si aucune de -cftuSUX ni --sort.

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

Alternative avec sort

Intersection de deux listes:

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

différence symétrique de deux listes:

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

Prime

Joue avec ;)

cd $(mktemp -d) && mkdir {one,two} && touch {one,two}/file_{1,2}{0..9} && touch two/file_3{0..9}

2
Au lieu d'un complément , je pense que c'est ce qu'on appelle généralement la différence symétrique .
Andrew Lazarus

29

Utilisez la commcommande:

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

"sort" n'est pas vraiment nécessaire mais je l'inclus toujours avant d'utiliser "comm" au cas où.


5
Il est bon de l'inclure car il a besoin d'être trié, et il n'a utilisé que ls comme exemple.
Thor84no

3

Une alternative moins efficace (que comm):

cat <(ls 1 | sort -u) <(ls 2 | sort -u) | uniq -d

1
Si vous utilisez / bin Debian / tableau de bord ou un autre shell Bash non dans vos scripts, vous pouvez la sortie de commandes de la chaîne en utilisant entre parenthèses: (ls 1; ls 2) | sort -u | uniq -d.
azote

1
@ MikaëlMayer Vous devez marquer le nom de la personne à laquelle vous répondez, sinon on suppose que vous parlez de moi.
Benubird

@ azote MikaëlMayer a raison - le chainging sort -u | uniq -dne fait rien, car le tri a supprimé les doublons avant qu'uniq ne commence à les rechercher. Je pense que vous n'avez pas compris ce que fait ma commande.
Benubird

@Benubird Je n'ai pas pu obtenir votre commande cat <(ls 1 | sort -u) <(ls 2 | sort -u) | uniq -dpour afficher quoi que ce soit non plus. Ma commande doit lire (ls 1; ls 2) | sort | uniq -d, sans le -u, pour afficher l'intersection de la liste. @ MikaëlMayer avait raison de dire que ma commande initiale était interrompue.
azote

@ azote La raison pour laquelle j'utilise cat, c'est que je veux que ce soit une solution généralisable, afin que vous puissiez la remplacer lspar autre chose, par exemple find. Votre solution ne le permet pas, car si l'une des commandes renvoie deux lignes identiques, elle la récupère comme un doublon. Le mien fonctionne même si l'utilisateur veut faire ls 1/*et comparer tous les fichiers dans les sous-répertoires. Sinon, oui, ça marche aussi. Il est possible que le mien soit spécifique à Bash.
Benubird

2

La jointure est une autre bonne option en fonction de l'entrée et de la sortie souhaitée

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

-1

Il y a une autre question de Stackoverflow "Intersection de tableau dans bash", qui est marquée comme une copie de ceci. Ce n'est pas tout à fait la même chose, à mon avis, car cette question parle de comparer deux tableaux bash, alors que cette question se concentre sur les fichiers bash. Une réponse en une ligne à l'autre question, qui est maintenant close, est la suivante:

# 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

L'utilitaire de communication effectue un tri alphanumérique, tandis que les réponses «Array intersection in bash» utilisent des nombres; d'où l'utilisation de "sort" et "sort -g".

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.