Comment pouvez-vous différencier deux pipelines dans Bash?

143

Comment pouvez-vous différencier deux pipelines sans utiliser de fichiers temporaires dans Bash? Supposons que vous ayez deux pipelines de commandes:

foo | bar
baz | quux

Et vous voulez trouver les diffdans leurs sorties. Une solution serait évidemment de:

foo | bar > /tmp/a
baz | quux > /tmp/b
diff /tmp/a /tmp/b

Est-il possible de le faire sans l'utilisation de fichiers temporaires dans Bash? Vous pouvez vous débarrasser d'un fichier temporaire en canalisant l'un des pipelines vers diff:

foo | bar > /tmp/a
baz | quux | diff /tmp/a -

Mais vous ne pouvez pas diriger les deux pipelines dans diff simultanément (pas de manière évidente, du moins). Existe-t-il une astuce intelligente /dev/fdpour faire cela sans utiliser de fichiers temporaires?

Adam Rosenfield
la source

Réponses:

146

Une ligne avec 2 fichiers tmp (pas ce que vous voulez) serait:

 foo | bar > file1.txt && baz | quux > file2.txt && diff file1.txt file2.txt

Avec bash , vous pouvez essayer:

 diff <(foo | bar) <(baz | quux)

 foo | bar | diff - <(baz | quux)  # or only use process substitution once

La 2ème version vous rappellera plus clairement quelle entrée était laquelle, en affichant
-- /dev/stdinvs ++ /dev/fd/63ou quelque chose, au lieu de deux fds numérotés.


Pas même un tube nommé n'apparaîtra dans le système de fichiers, du moins sur les systèmes d'exploitation où bash peut implémenter la substitution de processus en utilisant des noms de fichiers comme /dev/fd/63pour obtenir un nom de fichier que la commande peut ouvrir et lire pour réellement lire à partir d'un descripteur de fichier déjà ouvert que bash défini avant d'exécuter la commande. (c'est-à-dire que bash utilise pipe(2)avant fork, puis dup2pour rediriger de la sortie de quuxvers un descripteur de fichier d'entrée pour diff, sur fd 63.)

Sur un système sans "magique" /dev/fdou /proc/self/fd, bash pourrait utiliser des tubes nommés pour implémenter la substitution de processus, mais il les gérerait au moins lui-même, contrairement aux fichiers temporaires, et vos données ne seraient pas écrites sur le système de fichiers.

Vous pouvez vérifier comment bash implémente la substitution de processus avec echo <(true)pour imprimer le nom de fichier au lieu de le lire. Il imprime /dev/fd/63sur un système Linux typique. Ou pour plus de détails sur exactement les appels système que bash utilise, cette commande sur un système Linux tracera les appels système de fichiers et de descripteurs de fichiers.

strace -f -efile,desc,clone,execve bash -c '/bin/true | diff -u - <(/bin/true)'

Sans bash, vous pourriez créer un tube nommé . Utilisez -pour dire diffde lire une entrée de STDIN, et utilisez le tube nommé comme l'autre:

mkfifo file1_pipe.txt
foo|bar > file1_pipe.txt && baz | quux | diff file1_pipe.txt - && rm file1_pipe.txt

Notez que vous ne pouvez diriger qu'une seule sortie vers plusieurs entrées avec la commande tee:

ls *.txt | tee /dev/tty txtlist.txt 

La commande ci-dessus affiche la sortie de ls * .txt vers le terminal et la renvoie dans le fichier texte txtlist.txt.

Mais avec la substitution de processus, vous pouvez utiliser teepour alimenter les mêmes données dans plusieurs pipelines:

cat *.txt | tee >(foo | bar > result1.txt)  >(baz | quux > result2.txt) | foobar
VonC
la source
5
même sans bash, vous pouvez utiliser des mkfifo a; cmd >a& cmd2|diff a -; rm a
fifo
Vous pouvez utiliser un tuyau régulier pour l' un des args: pipeline1 | diff -u - <(pipeline2). Ensuite, la sortie vous rappellera plus clairement quelle entrée était laquelle, en affichant -- /dev/stdinvs. ++ /dev/fd/67ou quelque chose, au lieu de deux fds numérotés.
Peter Cordes
process substitution ( foo <( pipe )) ne modifie pas le système de fichiers. Le tuyau est anonyme ; il n'a pas de nom dans le système de fichiers . Le shell utilise l' pipeappel système pour le créer, non mkfifo. Utilisez strace -f -efile,desc,clone,execve bash -c '/bin/true | diff -u - <(/bin/true)'pour tracer les appels système de fichier et de descripteur de fichier si vous voulez voir par vous-même. Sous Linux, /dev/fd/63fait partie du /procsystème de fichiers virtuel; il a automatiquement des entrées pour chaque descripteur de fichier, et ce n'est pas une copie du contenu. Donc, vous ne pouvez pas appeler cela un "fichier temporaire" sauf si cela foo 3<bar.txtcompte
Peter Cordes
@PeterCordes Bons points. J'ai inclus votre commentaire dans la réponse pour plus de visibilité.
VonC
1
@PeterCordes Je vous laisse toute modification: c'est ce qui rend Stack Overflow intéressant: n'importe qui peut "corriger" une réponse.
VonC
127

Dans bash, vous pouvez utiliser des sous-shell, pour exécuter les pipelines de commandes individuellement, en plaçant le pipeline entre parenthèses. Vous pouvez ensuite les préfixer avec <pour créer des canaux nommés anonymes que vous pouvez ensuite passer à diff.

Par exemple:

diff <(foo | bar) <(baz | quux)

Les canaux nommés anonymes sont gérés par bash afin qu'ils soient créés et détruits automatiquement (contrairement aux fichiers temporaires).

BenM
la source
1
Beaucoup plus détaillé que ma rédaction sur la même solution - lot anonyme -. +1
VonC
4
C'est ce qu'on appelle la substitution de processus dans Bash.
Franklin Yu
5

Certaines personnes arrivant sur cette page pourraient rechercher un diff ligne par ligne, pour lequel commou grep -fdevrait être utilisé à la place.

Une chose à souligner est que, dans tous les exemples de réponse, les différences ne commenceront pas tant que les deux flux ne seront pas terminés. Testez ceci avec par exemple:

comm -23 <(seq 100 | sort) <(seq 10 20 && sleep 5 && seq 20 30 | sort)

Si c'est un problème, vous pouvez essayer sd (stream diff), qui ne nécessite pas de tri (comme le commfait) ni de substitution de processus comme les exemples ci-dessus, est des ordres ou de l'ampleur plus rapide que grep -f et prend en charge les flux infinis.

L'exemple de test que je propose serait écrit comme ceci dans sd:

seq 100 | sd 'seq 10 20 && sleep 5 && seq 20 30'

Mais la différence est que seq 100cela serait tout de seq 10suite différent. Notez que si l'un des flux est a tail -f, la différence ne peut pas être effectuée avec la substitution de processus.

Voici un article de blog que j'ai écrit sur les différents flux sur le terminal, qui présente sd.

mlg
la source