Warum ist /dev/stderr bei der Umleitung zu einer Pipe in Cygwin ungültig?

Warum ist /dev/stderr bei der Umleitung zu einer Pipe in Cygwin ungültig?

Ich bin kürzlich über ein überraschendes Problem mit einem Fehler /dev/stderrauf einem aktuellen Cygwin gestolpert , der auch auf einer ausgereiften Debian-Installation vorhanden ist . (Bearbeiten: Anders als ich ursprünglich dachte, weist mein Debian-System diesen Fehler nicht auf, sondern erzeugt einfach die gewünschte Ausgabe. Ich muss jetzt davon ausgehen, dass dies ein Cygwin-Fehler ist.)

Hintergrund: Ich verwende Tools, die Tausende von Ausgabezeilen erzeugen (insbesondere Versionskontrollsysteme auf einem großen Produktionssystem). Ich führe sie skriptgesteuert aus und wollte die verrauschte Tool-Ausgabe optional in eine Protokolldatei umleiten. Eine einfache Lösung schien darin zu bestehen, ihre Ausgabe (stderr und stdout) immer an ein Dateisystemziel umzuleiten, das in einer Umgebungsvariable gespeichert war. Wenn eine Ausgabe an das Terminal (oder ein benutzergesteuertes Ziel) gewünscht wäre, DBG_STDERRwäre das Ziel einfach „/dev/stderr“, andernfalls ein temporärer Dateiname. Eine typische Tool-Ausführungszeile würde dann aussehen wie noisy_command >> "$DBG_STDERR" 2>&1.

Das funktioniert einwandfrei, sofern ich die Ausgabe des Skripts nicht weiterleite. Hier ist eine minimale Reproduktion:

$ uname -a
CYGWIN_NT-6.1-WOW xxxxxxx 2.8.1(0.312/5/3) 2017-07-03 14:06 i686 Cygwin

$ bash --version
GNU bash, version 4.4.12(3)-release (i686-pc-cygwin)

$ cat say-something.sh
#!/bin/sh
echo something > /dev/stderr

$ (x=$(./say-something.sh 2> /dev/stderr)) 2>&1 |cat
./say-something.sh: line 2: /dev/stderr: No such file or directory

$ (x=$(./say-something.sh 2> /dev/stderr)) 2>&1
something

$ (x=$(./say-something.sh 2> /dev/stderr))  |cat
something

$ x=$(./say-something.sh 2> /dev/stderr) 2>&1 |cat
something

Natürlich sehen all die Umleitungen und verschachtelten Shells außerhalb des Kontexts komisch aus. Die zusätzliche Shell ist notwendig, da say-something.sh tatsächlich von einem anderen Skript aufgerufen würde. Die redundante Umleitung von fd 2 nach stderr ist der „Schalter“, um eine optionale Umleitung zu einer Datei zu ermöglichen (/dev/stderr oder ein anderer Pfad ist tatsächlich der konfigurierbare Inhalt einer Variablen).

Es scheint, als seien alle Bestandteile dieser Pipeline notwendig, wie die Experimente nach dem gescheiterten Beispiel zeigen: Sie alle gelingen.

  • Wir brauchen die letzte Pipe von stdout
  • Wir benötigen das Kopieren von stderr nach stdout durch den Anrufer
  • Wir benötigen die äußere Hülle um die Befehlsersetzung.

Antwort1

Der Name /dev/stderrist tatsächlich gültig, wenn auf eine Pipe umgeleitet wird. Was möglicherweise nicht möglich ist, ist das /dev/stderrdirekte Öffnen des endgültigen Ziels. Sehen Sie einfach nach:

$ (echo Testing testing > /dev/stderr) |& cat
Testing testing

Das durch |oder erzeugte Rohr |&ist in der Regel einanonyme Pfeife; der angezeigte Nameentspricht keinem Objekt im DateisystemZur Veranschaulichung können Sie etwas Einfaches ausprobieren wie:

$ ls -la /dev/fd/ |& cat
total 0
dr-x------ 2 alexp alexp  0 Jul  6 18:23 .
dr-xr-xr-x 9 alexp alexp  0 Jul  6 18:23 ..
lrwx------ 1 alexp alexp 64 Jul  6 18:23 0 -> /dev/pts/4
l-wx------ 1 alexp alexp 64 Jul  6 18:23 1 -> pipe:[1058859]
l-wx------ 1 alexp alexp 64 Jul  6 18:23 2 -> pipe:[1058859]
lr-x------ 1 alexp alexp 64 Jul  6 18:23 3 -> /proc/4335/fd

Es ist sehr ungewöhnlich, zu versuchen, das (endgültige) Ziel von zu öffnen /dev/stderr; der Name /dev/stderrwird angegeben, umvermeidensich die Mühe zu machen, das eigentliche Ziel herauszufinden.

Antwort2

Ich denke, das Problem ist der gestartete Readline-Prozess. Er erhält seine eigene Pipe für die Umleitung, die geschlossen wird, wenn der Prozess stoppt (die PID, die Sie erhalten, ist nicht die der Shell, sondern die des Readlink-Prozesses). Die Pipe wird ungültig, wenn der Prozess beendet wird. Versuchen Sie, FIFOs/Named Pipes zu verwenden.

verwandte Informationen