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) ?

StackOverflow https://stackoverflow.com/questions/60942

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
Était-ce utile?

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 et ksh (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 et ksh 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 ;le tee 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 version 93u+) permet d'utiliser des mots sans argument wait 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 avec wait $!, mais sans argument wait fait pas fonctionne, ce qui le rend impropre à une commande avec plusieurs substitutions de processus de sortie.

  • Cependant, bash et ksh 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 de ksh 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 ni ksh ni zsh soutenir cela.
  • 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.

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.



bashSolution 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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top