Comment puis-je envoyer la sortie standard d'un processus à plusieurs processus en utilisant des canaux (de préférence sans nom) sous Unix (ou Windows) ?
Question
Je voudrais rediriger la sortie standard du processus proc1 vers deux processus proc2 et proc3 :
proc2 -> stdout
/
proc1
\
proc3 -> stdout
J'ai essayé
proc1 | (proc2 & proc3)
mais ça ne semble pas fonctionner, c'est-à-dire
echo 123 | (tr 1 a & tr 1 b)
écrit
b23
vers la sortie standard au lieu de
a23
b23
La solution
Note de l'éditeur:
- >(…)
est un substitution de processus c'est un fonctionnalité de coque non standard de quelques Shells compatibles POSIX : bash
, ksh
, zsh
.
- Cette réponse envoie accidentellement la sortie de la substitution du processus de sortie via le pipeline aussi: echo 123 | tee >(tr 1 a) | tr 1 b
.
- Les résultats des substitutions de processus seront entrelacés de manière imprévisible et, sauf dans zsh
, le pipeline peut se terminer avant les commandes à l'intérieur >(…)
faire.
Sous Unix (ou sur Mac), utilisez le tee
commande:
$ echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null
b23
a23
Habituellement, vous utiliseriez tee
Pour rediriger la sortie vers plusieurs fichiers, mais en utilisant> (...), vous pouvez rediriger vers un autre processus.Donc, en général,
$ proc1 | tee >(proc2) ... >(procN-1) >(procN) >/dev/null
tu feras ce que tu veux.
Sous Windows, je ne pense pas que le shell intégré ait un équivalent.de Microsoft Windows PowerShell a un tee
commande cependant.
Autres conseils
Comme l'a dit DF, bash
permet d'utiliser le >(…)
build exécutant une commande à la place d’un nom de fichier.(Il y a aussi le <(…)
construire pour remplacer le sortir d'une autre commande à la place d'un nom de fichier, mais cela n'a plus d'importance maintenant, je le mentionne juste pour être complet).
Si vous n'avez pas bash ou si vous utilisez un système avec une ancienne version de bash, vous pouvez faire manuellement ce que fait bash, en utilisant des fichiers FIFO.
La manière générique d’obtenir ce que vous voulez est la suivante :
- décidez combien de processus doivent recevoir le résultat de votre commande et créez autant de FIFO, de préférence sur un dossier temporaire global :
subprocesses="a b c d" mypid=$$ for i in $subprocesses # this way we are compatible with all sh-derived shells do mkfifo /tmp/pipe.$mypid.$i done
- démarrez tous vos sous-processus en attente d'entrée des FIFO :
for i in $subprocesses do tr 1 $i </tmp/pipe.$mypid.$i & # background! done
- exécutez votre commande en passant aux FIFO :
proc1 | tee $(for i in $subprocesses; do echo /tmp/pipe.$mypid.$i; done)
- enfin, supprimez les FIFO :
for i in $subprocesses; do rm /tmp/pipe.$mypid.$i; done
NOTE:pour des raisons de compatibilité, je ferais le $(…)
avec des guillemets, mais je n'ai pas pu le faire en écrivant cette réponse (le backquote est utilisé dans SO).Normalement, le $(…)
est assez ancien pour fonctionner même dans les anciennes versions de ksh, mais si ce n'est pas le cas, joignez le …
partie en backquotes.
Unix (bash
, ksh
, zsh
)
La réponse de dF. contient le graine d'une réponse basée sur tee
et sortir substitutions de processus
(>(...)
) que peut-être ou peut-être pas travail, selon vos besoins :
Notez que les substitutions de processus sont un non standard caractéristique qui (principalement) des coquilles POSIX-FEATURES uniquement telles que dash
(qui fait office de /bin/sh
sur Ubuntu, par exemple), faites pas soutien.Ciblage des scripts Shell /bin/sh
devrait pas compter sur eux.
echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null
Le pièges de cette approche sont :
comportement de sortie imprévisible et asynchrone:les flux de sortie des commandes à l'intérieur des substitutions du processus de sortie
>(...)
s'entrelacent de manière imprévisible.Dans
bash
etksh
(contrairement àzsh
- mais voir exception ci-dessous) :- la sortie peut arriver après la commande est terminée.
- les commandes suivantes peuvent commencer à s'exécuter avant les commandes dans les substitutions de processus sont terminées -
bash
etksh
faire pas attendez que les processus générés par substitution du processus de sortie se terminent, au moins par défaut. - jmb le dit bien dans un commentaire sur la réponse de dF. :
sachez que les commandes ont démarré à l'intérieur
>(...)
sont dissociés de la coque d'origine et vous ne pouvez pas facilement déterminer quand ils se termineront ;letee
se terminera après avoir tout écrit, mais les processus remplacés consommeront toujours les données de divers tampons du noyau et des E/S de fichiers, ainsi que le temps nécessaire à leur traitement interne des données.Vous pouvez rencontrer des conditions de concurrence si votre enveloppe externe continue à s'appuyer sur tout ce qui est produit par les sous-processus.
zsh
est le seul shell qui fait par défaut, attendez la fin des processus exécutés dans les substitutions de processus de sortie, sauf Si c'est stderr qui est redirigé vers un (2> >(...)
).ksh
(au moins à partir de la version93u+
) permet d'utiliser des mots sans argumentwait
pour attendre la fin des processus générés par la substitution du processus de sortie.
Notez que dans une session interactive, cela pourrait entraîner l'attente de tout message en attente. travaux d'arrière-plan mais aussi.bash v4.4+
je peux attendre le plus récemment lancement de la substitution du processus de sortie avecwait $!
, mais sans argumentwait
fait pas fonctionne, ce qui le rend impropre à une commande avec plusieurs substitutions de processus de sortie.Cependant,
bash
etksh
peut être forcé attendre en redirigeant la commande vers| cat
, mais notez que cela rend la commande exécutée dans un sous-shell. Mises en garde:ksh
(à partir deksh 93u+
) ne prend pas en charge l'envoi stderr à une substitution de processus de sortie (2> >(...)
);une telle tentative est ignoré en silence.Alors que
zsh
est (louablement) synchrone par défaut avec le (beaucoup plus courant) sortie standard substitutions de processus de sortie, même les| cat
la technique ne peut pas les rendre synchrones avec stderr substitutions de processus de sortie (2> >(...)
).
Cependant, même si vous assurez exécution synchrone, le problème de sortie entrelacée de manière imprévisible restes.
La commande suivante, lorsqu'elle est exécutée dans bash
ou ksh
, illustre les comportements problématiques (vous devrez peut-être l'exécuter plusieurs fois pour voir les deux symptômes):Le AFTER
imprimera généralement avant sortie des substitutions de sortie, et la sortie de ces dernières peut être entrelacée de manière imprévisible.
printf 'line %s\n' {1..30} | tee >(cat -n) >(cat -n) >/dev/null; echo AFTER
En bref:
Garantir une séquence de sortie particulière par commande :
- Ni l'un ni l'autre
bash
niksh
nizsh
soutenir cela.
- Ni l'un ni l'autre
Exécution synchrone :
- Faisable, sauf avec stderr- substitutions de processus de sortie d'origine :
- Dans
zsh
, ils sont invariablement asynchrone. - Dans
ksh
, ils ne fonctionne pas du tout.
- Dans
- Faisable, sauf avec stderr- substitutions de processus de sortie d'origine :
Si vous pouvez vivre avec ces limitations, l'utilisation de substitutions de processus de sortie est une option viable (par exemple, si tous écrivent dans des fichiers de sortie séparés).
Noter que tzot est une solution beaucoup plus lourde, mais potentiellement conforme à POSIX présente également un comportement de sortie imprévisible;cependant, en utilisant wait
vous pouvez vous assurer que les commandes suivantes ne commencent pas à s'exécuter tant que tous les processus en arrière-plan ne sont pas terminés.
Voir en bas pour une implémentation de sortie sérialisée plus robuste, synchrone.
Le seul direct bash
solution avec un comportement de sortie prévisible est la suivante, qui est cependant excessivement lent avec de grands ensembles d'entrées, car les boucles shell sont intrinsèquement lentes.
Notez également que ceci suppléants les lignes de sortie des commandes cibles.
while IFS= read -r line; do
tr 1 a <<<"$line"
tr 1 b <<<"$line"
done < <(echo '123')
Unix (en utilisant GNU Parallèle)
Installation GNOU parallel
permet un solution robuste avec sortie sérialisée (par commande) qui permet en outre exécution parallèle:
$ echo '123' | parallel --pipe --tee {} ::: 'tr 1 a' 'tr 1 b'
a23
b23
parallel
par défaut, garantit que les sorties des différentes commandes ne s'entrelacent pas (ce comportement peut être modifié - voir man parallel
).
Note:Certaines distributions Linux sont livrées avec un différent parallel
utilitaire, qui ne fonctionnera pas avec la commande ci-dessus ;utiliser parallel --version
pour déterminer lequel, le cas échéant, vous possédez.
les fenêtres
Réponse utile de Jay Bazuzi montre comment le faire dans PowerShell.Cela dit:sa réponse est l'analogue du bouclage bash
répondez ci-dessus, ce sera excessivement lent avec de grands ensembles d'entrées et aussi suppléants les lignes de sortie des commandes cibles.
bash
Solution Unix basée sur , mais par ailleurs portable, avec exécution synchrone et sérialisation des sorties
Ce qui suit est une mise en œuvre simple mais raisonnablement robuste de l'approche présentée dans La réponse de Tsot qui fournit en outre :
- exécution synchrone
- sortie sérialisée (groupée)
Bien qu'il ne soit pas strictement conforme à POSIX, car il s'agit d'un bash
script, ça devrait être portable sur n'importe quelle plate-forme Unix dotée de bash
.
Note:Vous pouvez trouver une implémentation plus complète publiée sous la licence MIT dans cet essentiel.
Si vous enregistrez le code ci-dessous en tant que script fanout
, rendez-le exécutable et insérez-y votre PATH
, la commande de la question fonctionnerait comme suit :
$ echo 123 | fanout 'tr 1 a' 'tr 1 b'
# tr 1 a
a23
# tr 1 b
b23
fanout
code source du script:
#!/usr/bin/env bash
# The commands to pipe to, passed as a single string each.
aCmds=( "$@" )
# Create a temp. directory to hold all FIFOs and captured output.
tmpDir="${TMPDIR:-/tmp}/$kTHIS_NAME-$$-$(date +%s)-$RANDOM"
mkdir "$tmpDir" || exit
# Set up a trap that automatically removes the temp dir. when this script
# exits.
trap 'rm -rf "$tmpDir"' EXIT
# Determine the number padding for the sequential FIFO / output-capture names,
# so that *alphabetic* sorting, as done by *globbing* is equivalent to
# *numerical* sorting.
maxNdx=$(( $# - 1 ))
fmtString="%0${#maxNdx}d"
# Create the FIFO and output-capture filename arrays
aFifos=() aOutFiles=()
for (( i = 0; i <= maxNdx; ++i )); do
printf -v suffix "$fmtString" $i
aFifos[i]="$tmpDir/fifo-$suffix"
aOutFiles[i]="$tmpDir/out-$suffix"
done
# Create the FIFOs.
mkfifo "${aFifos[@]}" || exit
# Start all commands in the background, each reading from a dedicated FIFO.
for (( i = 0; i <= maxNdx; ++i )); do
fifo=${aFifos[i]}
outFile=${aOutFiles[i]}
cmd=${aCmds[i]}
printf '# %s\n' "$cmd" > "$outFile"
eval "$cmd" < "$fifo" >> "$outFile" &
done
# Now tee stdin to all FIFOs.
tee "${aFifos[@]}" >/dev/null || exit
# Wait for all background processes to finish.
wait
# Print all captured stdout output, grouped by target command, in sequences.
cat "${aOutFiles[@]}"
Depuis @dF :J'ai mentionné que PowerShell avait du tee, j'ai pensé montrer un moyen de le faire dans PowerShell.
PS > "123" | % {
$_.Replace( "1", "a"),
$_.Replace( "2", "b" )
}
a23
1b3
Notez que chaque objet sortant de la première commande est traité avant la création de l'objet suivant.Cela peut permettre une adaptation à des entrées très importantes.
une autre façon de faire serait,
eval `echo '&& echo 123 |'{'tr 1 a','tr 1 b'} | sed -n 's/^&&//gp'`
sortir:
a23
b23
pas besoin de créer un sous-shell ici