Comment émuler la substitution de processus dans Dash?


18

Dans bash, je peux utiliser la substitution de processus et traiter la sortie d'un processus comme s'il s'agissait d'un fichier enregistré sur le disque:

$ echo <(ls)
/dev/fd/63

$ ls -lAhF <(ls)
lr-x------ 1 root root 64 Sep 17 12:55 /dev/fd/63 -> pipe:[1652825]

malheureusement, la substitution de processus n'est pas prise en charge dans dash.

Quelle serait la meilleure façon d'émuler Process Substitutionen tiret?

Je ne veux pas enregistrer la sortie en tant que fichier temporaire quelque part ( /tmp/), puis je dois le supprimer. Existe-t-il une alternative?


Selon ce que vous essayez de faire, vous pourrez peut-être utiliser des tuyaux.
Eric Renouf

Honnêtement, si la portabilité n'est pas votre principale préoccupation ici, ne serait-il pas plus facile de simplement installer bashsur votre appareil?
Arkadiusz Drabczyk

1
L'exemple que vous fournissez dans l'avis de prime fait l'objet de cette réponse liée . Comme indiqué ici, une version simplifiée de la réponse de Gilles (en supposant la disponibilité de /dev/fdet en utilisant xz -cd <file>au lieu de cat <file> | xz -d) pourrait être xz -cd "$1" | { xz -cd "$2" | { diff /dev/fd/3 /dev/fd/4; } 3<&0; } 4<&0.
fra-san

1
@ fra-san - c'est en fait ce dont j'avais besoin. Vous pouvez en faire une réponse si vous le souhaitez. Merci.
Martin Vegter

Réponses:


4

La question dans l'avis de prime actuel:

l'exemple général est trop compliqué. Quelqu'un peut-il expliquer comment mettre en œuvre l'exemple suivant?diff <(cat "$2" | xz -d) <(cat "$1" | xz -d)

semble avoir une réponse ici .

Comme le montre la réponse de Gilles , l'idée générale est d'envoyer la sortie des commandes "producteur" à de nouveaux fichiers de périphérique 1 à différents stades d'un pipeline, en les mettant à la disposition des commandes "consommateurs", qui peuvent éventuellement prendre des noms de fichiers comme arguments ( en supposant que votre système vous donne accès aux descripteurs de fichiers en tant que /dev/fd/X).

La façon la plus simple de réaliser ce que vous recherchez est probablement:

xz -cd file1.xz | { xz -cd file2.xz | diff /dev/fd/3 -; } 3<&0

(Utiliser file1.xzà la place de "$1"pour la lisibilité et xz -cdau lieu de cat ... | xz -dcar une seule commande suffit).

La sortie de la première commande "producteur" xz -cd file1.xz, est dirigée vers une commande composée ( {...}); mais, au lieu d'être consommé immédiatement comme entrée standard de la commande suivante, il est dupliqué dans le descripteur de fichier 3et ainsi rendu accessible à tout ce qui se trouve dans la commande composée comme /dev/fd/3. La sortie de la deuxième commande "producteur" xz -cd file2.xz, qui ne consomme ni son entrée standard ni quoi que ce soit du descripteur de fichier 3, est ensuite dirigée vers la commande "consumer" diff, qui lit à partir de son entrée standard et de /dev/fd/3.

La duplication de la tuyauterie et des descripteurs de fichiers peut être ajoutée afin de fournir des fichiers de périphérique pour autant de commandes "producteurs" que nécessaire, par exemple:

xz -cd file1.xz | { xz -cd file2.xz | { diff /dev/fd/3 /dev/fd/4; } 4<&0; } 3<&0

Bien que cela puisse ne pas être pertinent dans le contexte de votre question spécifique, il convient de noter que:

  1. cmd1 <(cmd2) <(cmd3), cmd2 | { cmd3 | { cmd1 /dev/fd/3 /dev/fd/4; } 4<&0; } 3<&0Et ( cmd2 | ( cmd3 | ( cmd1 /dev/fd/3 /dev/fd/4 ) 4<&0 ) 3<&0 )avoir des effets potentiels sur l'environnement d'exécution initial.

  2. Contrairement à ce qui se passe dans cmd1 <(cmd2) <(cmd3), cmd3et cmd1dans cmd2 | { cmd3 | { cmd1 /dev/fd/3 /dev/fd/4; } 4<&0; } 3<&0ne pourra lire aucune entrée de l'utilisateur. Cela nécessitera d'autres descripteurs de fichiers. Par exemple, pour faire correspondre

    diff <(echo foo) <(read var; echo "$var")
    

    vous aurez besoin de quelque chose comme

    { echo foo | { read var 0<&9; echo "$var" | diff /dev/fd/3 -; } 3<&0; } 9<&0
    

1 Plus d'informations à ce sujet peuvent être trouvées sur U&L, par exemple dans Understanding / dev et ses sous-répertoires et fichiers .


12

Vous pouvez reproduire ce que fait la coque sous le capot en faisant la plomberie manuellement. Si votre système a des entrées, vous pouvez utiliser le brassage des descripteurs de fichiers: vous pouvez traduire/dev/fd/NNN

main_command <(produce_arg1) <(produce_arg2) >(consume_arg3) >(consume_arg4)

à

{ produce_arg1 |
  { produce_arg2 |
    { main_command /dev/fd5 /dev/fd6 /dev/fd3 /dev/fd4 </dev/fd/8 >/dev/fd/9; } 5<&0 3>&1 |
    consume_arg3; } 6<&0 4>&1; |
  consume_arg4; } 8<&0 9>&1

J'ai montré un exemple plus complexe pour illustrer plusieurs entrées et sorties. Si vous n'avez pas besoin de lire à partir de l'entrée standard et que la seule raison pour laquelle vous utilisez la substitution de processus est que la commande nécessite un nom de fichier explicite, vous pouvez simplement utiliser /dev/stdin:

main_command <(produce_arg1)
produce_arg1 | main_command /dev/stdin

Sans , vous devez utiliser un canal nommé . Un canal nommé est une entrée de répertoire, vous devez donc créer un fichier temporaire quelque part, mais ce fichier n'est qu'un nom, il ne contient aucune donnée./dev/fd/NNN

tmp=$(mktemp -d)
mkfifo "$tmp/f1" "$tmp/f2" "$tmp/f3" "$tmp/f4"
produce_arg1 >"$tmp/f1" &
produce_arg2 >"$tmp/f2" &
consume_arg3 <"$tmp/f3" &
consume_arg4 <"$tmp/f4" &
main_command "$tmp/f1" "$tmp/f2" "$tmp/f3" "$tmp/f4"
rm -r "$tmp"

2
</dev/fd/8 >/dev/fd/9ne sont pas équivalents à <&8 >&9sous Linux et devraient y être évités.
Stéphane Chazelas

@Gilles - Je suis confus par l'exemple complexe. Pourriez - vous s'il vous plaît montrer comment imiter: diff <(cat "$2" | xz -d) <(cat "$1" | xz -d)?
Martin Vegter

3

Quelqu'un peut-il expliquer comment mettre en œuvre l'exemple suivant?
diff <(cat "$2" | xz -d) <(cat "$1" | xz -d)

Je pense que les canaux nommés sont plus simples à gérer que les redirections, donc en termes plus simples:

mkfifo p1 p2               # create pipes
cat "$2" | xz -d > p1 &    # start the commands in the background
cat "$1" | xz -d > p2 &    #    with output to the pipes
diff p1 p2                 # run 'diff', reading from the pipes
rm p1 p2                   # remove them at the end

p1et p2sont des tuyaux nommés temporaires, ils peuvent être nommés n'importe quoi.

Il serait plus intelligent de créer les tuyaux /tmp, par exemple dans un répertoire comme le montre la réponse de Gilles , et notez que vous n'avez pas besoin catici, donc:

tmpdir=$(mktemp -d)
mkfifo "$tmpdir/p1" "$tmpdir/p2"
xz -d < "$2" > "$tmpdir/p1" &
xz -d < "$1" > "$tmpdir/p2" &
diff "$tmpdir/p1" "$tmpdir/p2"
rm -r "$tmpdir"

(Vous pourriez également vous en tirer sans les guillemets, car cela mktemprisque de créer de "beaux" noms de fichiers.)

La solution de tuyau a la mise en garde que si la commande principale ( diff) meurt avant de lire toutes les entrées, les processus d'arrière-plan peuvent rester suspendus.


0

Qu'en est-il de:

cat "$2" | xz -d | diff /dev/sdtin /dev/stderr 2<<EOT
`cat "$1" | xz -d`
EOT
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.