Was ist die portable (POSIX) Methode, um eine Prozesssubstitution zu erreichen?


25

Einige Shells bashunterstützen beispielsweise Process Substitution, mit der die Prozessausgabe als Datei dargestellt werden kann:

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

Dieses Konstrukt ist jedoch nicht POSIX und daher nicht portierbar. Wie kann eine POSIX- freundliche Prozesssubstitution erreicht werden (dh eine, die funktioniert /bin/sh) ?

Anmerkung: die Frage ist nicht , zu fragen , wie man diff zwei sortierten Dateien - , die nur ein konstruiertes Beispiel soll zeigen , Prozess Substitution !




1
POSIX muss keine /bin/shPOSIX-Shell sein, sondern es muss shirgendwo ein Befehl aufgerufen werden, der in der richtigen Umgebung POSIX-kompatibel ist. Unter Solaris 10 /bin/shhandelt es sich beispielsweise nicht um eine POSIX-Shell, sondern um eine alte Bourne-Shell, in der sich die POSIX-Shell befindet /usr/xpg4/bin/sh.
Stéphane Chazelas

Antworten:


17

Diese Funktion wurde von ksh(erstmals in ksh86 dokumentiert) eingeführt und nutzte die /dev/fd/nFunktion (in einigen BSDs und AT & T-Systemen zuvor unabhängig hinzugefügt). In kshund bis ksh93u würde es nicht funktionieren, wenn Ihr System nicht die Unterstützung für / dev / fd / n hätte. Ab zsh, bash und ksh93u+höher können temporäre Named Pipes (Named Pipes, die meiner Meinung nach in SysIII hinzugefügt wurden) verwendet werden, bei denen / dev / fd / n nicht verfügbar ist.

Auf Systemen, auf denen verfügbar ist (POSIX gibt diese nicht an), können Sie die Substitution selbst durchführen ( ) mit:/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

Unter ksh93Linux funktioniert dies jedoch nicht , da Shell-Pipes mit Socket-Paaren anstelle von Pipes implementiert werden und das Öffnen, /dev/fd/3bei dem fd 3 auf einen Socket zeigt, unter Linux nicht funktioniert.

Obwohl POSIX nicht spezifiziert . Es werden Named Pipes angegeben. Named Pipes funktionieren wie normale Pipes, nur dass Sie über das Dateisystem darauf zugreifen können. Das Problem hierbei ist, dass Sie temporäre Dateien erstellen und diese anschließend bereinigen müssen. Dies ist schwierig, da POSIX über keinen Standardmechanismus (wie er auf einigen Systemen zu finden ist) verfügt, um temporäre Dateien oder Verzeichnisse zu erstellen und die Signalverarbeitung portabel durchzuführen (beim Auflegen oder Töten aufzuräumen) ist auch schwer tragbar zu machen./dev/fd/nmktemp -d

Sie könnten etwas tun wie:

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"

(kümmert sich hier nicht um die Signalverarbeitung).


Das Named-Pipe-Beispiel ist mir völlig klar (ich denke, 10 ist eine willkürliche Grenze?), Aber ich kann Ihr /dev/fd/nBeispiel nicht herausfinden . Warum wird Deskriptor 4 zweimal geschlossen? (Und so ist Deskriptor 3.) Ich bin verloren.
Wildcard

@Wildcard, es ist geschlossen für Befehle, die es nicht benötigen. Hier braucht nur diff das fd 3. Keines braucht das fd 4, es wird nur verwendet, um das ursprüngliche stdin zu verbreiten cmd2( dup2(0,4)auf der Außenseite {...}, restauriert mit dup2(4,0)für das Innere {...}).
Stéphane Chazelas

Sie könnten mktemp -ddazu beitragen, sicherzustellen, dass Sie FIFOs erhalten, da niemand in Ihr brandneues zufälliges temporäres Verzeichnis schreiben sollte.
Daniel H

@DanielH erwähne ich schon mktemp -d. Dies ist jedoch kein Standard- / POSIX-Befehl.
Stéphane Chazelas

Huh, das habe ich nicht gemerkt. Hoppla. Die schnelle Suche scheint zu zeigen, dass die meisten Systeme dies unterstützen, so dass es möglicherweise noch portabel ist, aber es ist kein POSIX.
Daniel H

-1

Falls erforderlich, um den Verlust von Variablen im Nutzwert zu vermeiden cmd | while read A B C, anstelle von:

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

Sie können verwenden:

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

Um die Frage zu beantworten:

sort file1 | diff /dev/stdin /dev/stdout 2<<EOT
`sort file2`
EOT
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.