web-dev-qa-db-fra.com

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

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

foo | bar
baz | quux

Et vous voulez trouver le diff dans 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 utiliser de fichiers temporaires dans Bash? Vous pouvez vous débarrasser d'un fichier temporaire en canalisant dans l'un des pipelines pour différencier:

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). Y a-t-il une astuce astucieuse impliquant /dev/fd pour ce faire sans utiliser de fichiers temporaires?

135
Adam Rosenfield

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 cependant:

 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 montrant
-- /dev/stdin Contre ++ /dev/fd/63 Ou quelque chose, au lieu de deux fds numérotés.


Même un canal nommé n'apparaîtra pas 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/63 Pour obtenir un nom de fichier que la commande peut ouvrir et lire pour réellement lire à partir d'un déjà- ouvrir le descripteur de fichier qui a été configuré avant d'exécuter la commande. (ie bash utilise pipe(2) avant fork, puis dup2 pour rediriger de la sortie de quux vers un descripteur de fichier d'entrée pour diff, sur fd 63 .)

Sur un système sans /dev/fd Ou /proc/self/fd "Magique", bash pourrait utiliser des canaux 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 le seraient pas ' t être écrit dans 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/63 Sur un système Linux typique. Ou pour plus de détails sur les appels système utilisés par bash, cette commande sur un système Linux suivra les appels système des fichiers et des 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 à diff de lire une entrée de STDIN et utilisez le canal 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 le tee commander:

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

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

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

cat *.txt | tee >(foo | bar > result1.txt)  >(baz | quux > result2.txt) | foobar
136
VonC

Dans bash, vous pouvez utiliser des sous-coquilles, pour exécuter les pipelines de commandes individuellement, en enfermant 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).

119
BenM

Certaines personnes arrivant sur cette page peuvent rechercher un diff ligne par ligne, pour lequel comm ou grep -f devrait être utilisé à la place.

Une chose à souligner est que, dans tous les exemples de réponse, les différences ne démarreront pas avant la fin des deux flux. Testez cela 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 comm le fait) ni de substitution de processus comme les exemples ci-dessus, est d'ordre ou de magnitude plus rapide que grep -f et prend en charge des 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 100 serait différent de seq 10 tout de suite. Notez que si l'un des flux est un tail -f, le diff ne peut pas être fait avec la substitution de processus.

Voici un blogpost J'ai écrit sur les différents flux sur le terminal, qui présente sd.

5
mlg