tiret: Pipe STDIN à plusieurs commands et leur sortie à STDOUT dans l'ordre défini

Au début, je pensais que cette réponse était la solution, mais maintenant je pense avoir besoin d'un file temporaire comme tampon.

Cela fonctionne de manière non fiable:

#!/bin/sh echo 'OK' | { { tee /dev/fd/3 | head --bytes=1 >&4 } 3>&1 | tail --bytes=+2 >&4 } 4>&1 

Quand j'exécute ça dans un terminal, j'ai parfois:

D'accord

et parfois j'obtiens:

K
O

Semble totalement random. Donc, comme une solution de contournement, j'écris la sortie de la tail à un file et de le relire à stdout après que le tuyau est terminé.

 #!/bin/sh echo 'OK' | { { tee /dev/fd/3 | head --bytes=1 >&4 } 3>&1 | tail --bytes=+2 >file } 4>&1 cat file 

Cela peut-il se faire au dash sans files temporaires? Les variables shell en tant que memory tampon ne sont pas non plus une option car la sortie peut contenir des octets NUL.

La meilleure solution est d'utiliser des files temporaires. Cela rend le code lisible et facile à comprendre lorsque la substitution de process n'est pas une option.

 tmpfile=$(mktemp) producer | tee "$tmpfile" | consumer1 consumer2 <"$tmpfile" rm -f "$tmpfile" 

ou même

 tmpfile=$(mktemp) producer >"$tmpfile" consumer1 <"$tmpfile" consumer2 <"$tmpfile" rm -f "$tmpfile" 

Si vous vouliez faire fonctionner les consommateurs et le producteur en parallèle, mais sérialiser la sortie des consommateurs, vous auriez besoin de retarder la sortie du deuxième consommateur. Pour cela, vous devez stocker sa sortie d'une manière ou d'une autre et la meilleure façon est avec un file temporaire.

Avec zsh :

 {cat =(producer > >(consumer1 >&3) | consumer2)} 3>&1 

bash a un problème en ce sens qu'il n'attend pas les commands de substitution de process, vous devriez donc utiliser des environnements de travail désagréables là-bas .

Ici, nous utilisons la forme de substitution de process =(...) pour stocker la sortie de comsumer2 dans un file temporaire et le comsumer2 ensuite. Nous ne pouvons pas faire cela pour plus de 2 consommateurs. Pour cela, nous aurions besoin de créer les files temporaires à la main.

Lorsque vous n'utilisez pas =(...) , nous devrons gérer le nettoyage des files temporaires à la main. Nous pouvons gérer cela en les créant et en les supprimant à l'avance afin de ne pas avoir à vous soucier des cas où le script est tué. Toujours avec zsh :

 tmp1=$(mktemp) && tmp2=$(mktemp) || exit { rm -f -- $tmp1 $tmp2 producer > >(consumer1) > >(consumer2 >&3) > >(consumer3 >&5) cat <&4 <&6 } 3> $tmp1 4< $tmp1 5> $tmp2 6< $tmp2 

Modifier (j'ai d'abord manqué le fait qu'une solution pour le dash était nécessaire)

Pour le dash (ou n'importe quel shell POSIX qui ne définit pas le drapeau close-on-exec sur fds au-dessus de 2 et utilise des pipes et non des paires de socket pour | ), et sur les systèmes avec le support /dev/fd/x :

 tmp1=$(mktemp) && tmp2=$(mktemp) || exit { rm -f -- "$tmp1" "$tmp2" { { { producer | tee /dev/fd/4 /dev/fd/6 | consumer1 >&7 } 4>&1 | consumer2 >&3 } 6>&1 | consumer3 >&5 } 7>&1 cat - /dev/fd/6 <&4 } 3> "$tmp1" 4< "$tmp1" 5> "$tmp2" 6< "$tmp2" 

Cela fonctionnerait avec dash , bash , zsh , mksh , busybox sh , posh sous Linux, mais pas ksh93 . Cette approche ne peut pas aller au-delà de 4 consommateurs car nous sums limités aux fds 0 à 9.