Quelle est la méthode portable (POSIX) pour réaliser la substitution de processus?

25

Certains shells, comme bash, prennent en charge la substitution de processus qui est un moyen de présenter la sortie du processus sous forme de fichier, comme ceci:

$ diff <(sort file1) <(sort file2)

Cependant, cette construction n'est pas POSIX et, par conséquent, n'est pas portable. Comment peut-on réaliser la substitution de processus d'une manière respectueuse de POSIX (c'est-à-dire qui fonctionne /bin/sh) ?

Remarque: la question n'est pas de savoir comment différencier deux fichiers triés - ce n'est qu'un exemple artificiel pour démontrer la substitution de processus !

étoilé
la source
mywiki.wooledge.org/ProcessSubstitution a une note que mywiki.wooledge.org/NamedPipes peut être utilisé ..
Sundeep
2
Voir aussi unix.stackexchange.com/a/43536/117549
Jeff Schaller
1
POSIX ne nécessite pas /bin/shd'être un shell POSIX, seulement qu'il y ait une commande appelée shquelque part qui dans le bon environnement est compatible POSIX. Par exemple, sur Solaris 10, ce /bin/shn'est pas un shell POSIX, c'est un ancien shell Bourne et le shell POSIX est dedans /usr/xpg4/bin/sh.
Stéphane Chazelas

Réponses:

17

Cette fonctionnalité a été introduite par ksh(d'abord documentée dans ksh86) et utilisait la /dev/fd/nfonctionnalité (ajoutée indépendamment dans certains systèmes BSD et AT&T plus tôt). Dans kshet jusqu'à ksh93u, cela ne fonctionnerait que si votre système prenait en charge / dev / fd / n. zsh, bash et ksh93u+plus peuvent utiliser des canaux nommés temporaires (des canaux nommés ajoutés dans SysIII je crois) où / dev / fd / n ne sont pas disponibles.

Sur les systèmes où est disponible (POSIX ne le spécifie pas), vous pouvez effectuer vous-même la substitution de processus ( ) avec:/dev/fd/ndiff <(cmd1) <(cmd2)

{
  cmd1 4<&- | {
    # in here fd 3 points to the reading end of the pipe
    # from cmd1, while fd 0 has been restored from the original
    # stdin (saved on fd 4, now closed as no longer needed)

    cmd2 3<&- | diff /dev/fd/3 -

  } 3<&0 <&4 4<&- # restore the original stdin for cmd2

} 4<&0 # save a copy of stdin for cmd2

Cependant, cela ne fonctionne pas avec ksh93Linux car là-bas, les canaux shell sont implémentés avec des paires de sockets au lieu de canaux et l'ouverture /dev/fd/3où fd 3 pointe vers un socket ne fonctionne pas sous Linux.

Bien que POSIX ne le spécifie pas . Il spécifie les canaux nommés. Les canaux nommés fonctionnent comme les canaux normaux, sauf que vous pouvez y accéder à partir du système de fichiers. Le problème ici est que vous devez créer des fichiers temporaires et nettoyer par la suite, ce qui est difficile à faire de manière fiable, d'autant plus que POSIX n'a ​​pas de mécanisme standard (comme celui que l'on trouve sur certains systèmes) pour créer des fichiers ou des répertoires temporaires et effectuer la gestion des signaux de manière portable (pour nettoyer après avoir raccroché ou tué) est également difficile à faire de manière portable./dev/fd/nmktemp -d

Vous pourriez faire quelque chose comme:

tmpfifo() (
  n=0
  until
    fifo=$1.$$.$n
    mkfifo -m 600 -- "$fifo" 2> /dev/null
  do
    n=$((n + 1))
    # give up after 20 attempts as it could be a permanent condition
    # that prevents us from creating fifos. You'd need to raise that
    # limit if you intend to create (and use at the same time)
    # more than 20 fifos in your script
    [ "$n" -lt 20 ] || exit 1
  done
  printf '%s\n' "$fifo"
)

cleanup() { rm -f -- "$fifo"; }
fifo=$(tmpfifo /tmp/fifo) || exit

cmd2 > "$fifo" & cmd1 | diff - "$fifo"
rm -f -- "$fifo"

(ne s'occupe pas du traitement du signal ici).

Stéphane Chazelas
la source
L'exemple de canal nommé est complètement clair pour moi (je suppose que 10 est une limite arbitraire?) Mais je ne peux pas comprendre votre /dev/fd/nexemple. Pourquoi le descripteur 4 est-il fermé deux fois? (Et le descripteur 3 aussi.) Je suis perdu.
Wildcard
@Wildcard, il est fermé pour les commandes qui n'en ont pas besoin. Ici, seul diff a besoin de ce fd 3. Aucun n'a besoin du fd 4, il est uniquement utilisé pour propager le stdin d'origine cmd2( dup2(0,4)à l'extérieur {...}, restauré avec dup2(4,0)pour l'intérieur {...}).
Stéphane Chazelas
Vous pouvez utiliser mktemp -dpour vous assurer d'obtenir des FIFO, car personne ne devrait écrire dans votre tout nouveau répertoire temporaire aléatoire.
Daniel H
@DanielH, je l'ai déjà mentionné mktemp -d. Mais ce n'est pas une commande standard / POSIX.
Stéphane Chazelas
Huh, je ne m'en étais pas rendu compte. Oops. Une recherche rapide semble montrer que la plupart des systèmes le prennent en charge, il peut donc toujours être portable, mais ce n'est pas POSIX.
Daniel H
-1

Si nécessaire pour éviter la perte de variable dans l'utile cmd | while read A B C, au lieu de:

VAR="before"
while read A B C 
do
  VAR="$A $VAR"
done < <(cmd)
echo "$VAR"

vous pouvez utiliser:

VAR="before"
while read A B C 
do
  VAR="$A $VAR"
done << EndOfText
`cmd`
EndOfText
echo "$VAR"

Donc pour répondre à la question:

sort file1 | diff /dev/stdin /dev/stdout 2<<EOT
`sort file2`
EOT
defdefred
la source