Wie kann ich die Prozessersetzung in Dash emulieren?

18

In bashkann ich Process Substitution verwenden und die Ausgabe eines Prozesses so behandeln, als wäre es eine auf der Festplatte gespeicherte Datei:

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

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

Leider wird die Prozessersetzung in nicht unterstützt dash.

Was wäre der beste Weg, um Process Substitutionim Bindestrich zu emulieren ?

Ich möchte die Ausgabe nicht als temporäre Datei speichern ( /tmp/) und muss sie dann löschen. Gibt es einen alternativen Weg?

Martin Vegter
quelle
Je nachdem, was Sie versuchen, können Sie möglicherweise Pipes verwenden.
Eric Renouf
Wenn Portabilität hier nicht Ihr Hauptanliegen ist, wäre es dann nicht einfacher, sie einfach bashauf Ihrem Gerät zu installieren ?
Arkadiusz Drabczyk
1
Das Beispiel, das Sie in der Kopfgeldanzeige angeben, ist zufällig Gegenstand dieser verknüpften Antwort . Wie dort gezeigt, könnte eine vereinfachte Version von Gilles 'Antwort (unter der Annahme, dass verfügbar ist /dev/fdund xz -cd <file>stattdessen verwendet wird cat <file> | xz -d) lauten xz -cd "$1" | { xz -cd "$2" | { diff /dev/fd/3 /dev/fd/4; } 3<&0; } 4<&0.
-San
1
@ Fra-San - das ist eigentlich, was ich brauchte. Sie können es eine Antwort machen, wenn Sie wollen. Vielen Dank.
Martin Vegter

Antworten:

4

Die Frage im aktuellen Kopfgeldbescheid:

Das allgemeine Beispiel ist zu kompliziert. Kann jemand bitte erklären, wie man das folgende Beispiel implementiert?diff <(cat "$2" | xz -d) <(cat "$1" | xz -d)

scheint hier eine antwort zu haben .

Wie in Gilles 'Antwort gezeigt , besteht die allgemeine Idee darin, die Ausgabe von "Producer" -Befehlen in verschiedenen Phasen einer Pipeline an neue Gerätedateien 1 zu senden und sie "Consumer" -Befehlen zur Verfügung zu stellen, die möglicherweise Dateinamen als Argumente verwenden ( vorausgesetzt, Ihr System gibt Ihnen Zugriff auf die Dateideskriptoren als /dev/fd/X).

Der einfachste Weg, um das zu erreichen, wonach Sie suchen, ist wahrscheinlich:

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

(Verwenden file1.xzstatt "$1"für die Lesbarkeit und xz -cdstatt cat ... | xz -dweil ein einziger Befehl ausreicht).

Die Ausgabe des ersten "Producer" -Befehls xz -cd file1.xzwird an einen zusammengesetzten Befehl ( {...}) weitergeleitet. Anstatt jedoch sofort als Standardeingabe für den nächsten Befehl verwendet zu werden, wird sie in den Dateideskriptor dupliziert3 und somit für alle Elemente des zusammengesetzten Befehls als verfügbar gemacht /dev/fd/3. Die Ausgabe des zweiten "Producer" -Befehls, xz -cd file2.xzder weder seine Standardeingabe noch irgendetwas aus dem Dateideskriptor verbraucht 3, wird dann an den "Consumer" -Befehl weitergeleitet diff, der aus seiner Standardeingabe und aus liest /dev/fd/3.

Piping und Duplikation von Dateideskriptoren können hinzugefügt werden, um Gerätedateien für beliebig viele "Producer" -Befehle bereitzustellen, z.

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

Während es im Kontext Ihrer spezifischen Frage irrelevant sein mag, ist es erwähnenswert, dass:

  1. cmd1 <(cmd2) <(cmd3), cmd2 | { cmd3 | { cmd1 /dev/fd/3 /dev/fd/4; } 4<&0; } 3<&0Und ( cmd2 | ( cmd3 | ( cmd1 /dev/fd/3 /dev/fd/4 ) 4<&0 ) 3<&0 )haben verschiedene mögliche Auswirkungen auf die anfängliche Ausführungsumgebung.

  2. Im Gegensatz zu dem, was in passiert cmd1 <(cmd2) <(cmd3), cmd3und cmd1in cmd2 | { cmd3 | { cmd1 /dev/fd/3 /dev/fd/4; } 4<&0; } 3<&0wird keine Eingaben des Benutzers lesen können. Dafür sind weitere Dateideskriptoren erforderlich. Zum Beispiel, um zusammenzupassen

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

    du wirst sowas brauchen

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

1 Weitere Informationen hierzu finden Sie in U & L, z. B. in Understanding / dev und seinen Unterverzeichnissen und Dateien .

Fra-San
quelle
12

Sie können reproduzieren, was die Schale unter der Haube tut, indem Sie die Installation manuell ausführen. Wenn Ihr System Einträge enthält, können Sie die Dateideskriptormischung verwenden: Sie können übersetzen/dev/fd/NNN

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

zu

{ 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

Ich habe ein komplexeres Beispiel gezeigt, um mehrere Ein- und Ausgänge zu veranschaulichen. Wenn Sie nicht aus der Standardeingabe lesen müssen und die Prozessersetzung nur deshalb verwenden, weil für den Befehl ein expliziter Dateiname erforderlich ist, können Sie einfach Folgendes verwenden /dev/stdin:

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

Ohne müssen Sie eine Named Pipe verwenden . Eine Named Pipe ist ein Verzeichniseintrag. Sie müssen also irgendwo eine temporäre Datei erstellen, aber diese Datei ist nur ein Name, sie enthält keine Daten./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"
Gilles 'SO - hör auf böse zu sein'
quelle
2
</dev/fd/8 >/dev/fd/9sind nicht gleichbedeutend mit <&8 >&9unter Linux und sollten dort vermieden werden.
Stéphane Chazelas
@ Gilles - Das komplexe Beispiel verwirrt mich. Könnten Sie bitte zeigen, wie man: emuliert diff <(cat "$2" | xz -d) <(cat "$1" | xz -d)?
Martin Vegter
3

Kann jemand bitte erklären, wie man das folgende Beispiel implementiert?
diff <(cat "$2" | xz -d) <(cat "$1" | xz -d)

Ich denke, Named Pipes sind einfacher zu verstehen als Umleitungen. Einfach ausgedrückt:

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

p1und p2sind temporäre Named Pipes, sie könnten alles genannt werden.

Es wäre klüger, die Rohre in erstellen /tmp, zB in einem Verzeichnis wie Gilles' Antwort zeigt, und beachten Sie, dass Sie nicht brauchen cathier, so:

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"

(Sie könnten wahrscheinlich auch ohne die Anführungszeichen davonkommen, da mktempwahrscheinlich "nette" Dateinamen erstellt werden.)

Die Pipe-Lösung hat den Vorbehalt, dass diffdie Hintergrundprozesse hängen bleiben können , wenn der Hauptbefehl ( ) vor dem Lesen aller Eingaben abbricht.

ilkkachu
quelle
0

Wie wäre es mit:

cat "$2" | xz -d | diff /dev/sdtin /dev/stderr 2<<EOT
`cat "$1" | xz -d`
EOT
defädiert
quelle