Im Code “{exec> / dev / null; }> / dev / null ”Was passiert unter der Haube?

15

Wenn Sie eine Befehlsliste umleiten, die eine Exec-Umleitung enthält, scheint das exec> / dev / null danach nicht mehr angewendet zu werden, wie zum Beispiel mit:

{ exec >/dev/null; } >/dev/null; echo "Hi"

"Hi" wird gedruckt.

Ich hatte den Eindruck, dass die {}Befehlsliste nur dann als Subshell betrachtet wird, wenn sie Teil einer Pipeline ist. Daher sollte die Befehlsliste exec >/dev/nullin meiner Vorstellung immer noch in der aktuellen Shell-Umgebung angewendet werden.

Nun, wenn Sie es ändern:

{ exec >/dev/null; } 2>/dev/null; echo "Hi"

es erfolgt keine erwartete Ausgabe; Dateideskriptor 1 zeigt auch für zukünftige Befehle auf / dev / null. Dies wird durch erneutes Ausführen gezeigt:

{ exec >/dev/null; } >/dev/null; echo "Hi"

das wird keine Ausgabe geben.

Ich habe versucht, ein Skript zu erstellen und es zu zeichnen, bin mir aber immer noch nicht sicher, was genau hier passiert.

Was passiert an jedem Punkt in diesem Skript mit dem STDOUT-Dateideskriptor?

BEARBEITEN: Hinzufügen meiner Strace-Ausgabe:

read(255, "#!/usr/bin/env bash\n{ exec 1>/de"..., 65) = 65
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fcntl(1, F_GETFD)                       = 0
fcntl(1, F_DUPFD, 10)                   = 10
fcntl(1, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0
close(10)                               = 0
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fcntl(1, F_GETFD)                       = 0
fcntl(1, F_DUPFD, 10)                   = 10
fcntl(1, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0
dup2(10, 1)                             = 1
fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
close(10)                               = 0
fstat(1, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0
ioctl(1, TCGETS, 0x7ffee027ef90)        = -1 ENOTTY (Inappropriate ioctl for device)
write(1, "hi\n", 3)                     = 3
Joey Pabalinas
quelle
Das ist seltsam; Ich kann das nicht reproduzieren close(10). Können Sie auch Ihren gesamten Skriptinhalt posten, auf den Sie gerade zugegangen sind?
DepressedDaniel
@ DepressedDaniel Hier ist das vollständige Skript und Strace: Skript Strace
Joey Pabalinas
Sie haben einen Streuner ;danach }, der die Bedeutung von ändert, > /dev/nullum schließlich nicht auf die Verbundliste anzuwenden {}.
DepressedDaniel
@ DepressedDaniel Ah, du hast vollkommen recht! Jetzt ist die Ausgabe das, was ich erwarte; Danke für deine Antworten!
Joey Pabalinas

Antworten:

17

Lasst uns folgen

{ exec >/dev/null; } >/dev/null; echo "Hi"

Schritt für Schritt.

  1. Es gibt zwei Befehle:

    ein. { exec >/dev/null; } >/dev/null, gefolgt von

    b. echo "Hi"

    Die Shell führt zuerst den Befehl (a) und dann den Befehl (b) aus.

  2. Die Ausführung { exec >/dev/null; } >/dev/nullerfolgt wie folgt:

    ein. Zuerst führt die Shell die Umleitung durch >/dev/null und merkt sich, um sie rückgängig zu machen, wenn der Befehl endet .

    b. Dann wird die Shell ausgeführt { exec >/dev/null; }.

    c. Schließlich schaltet die Shell die Standardausgabe wieder auf den ursprünglichen Zustand zurück. (Dies ist derselbe Mechanismus wie bei ls -lR /usr/share/fonts >~/FontList.txtUmleitungen, die nur für die Dauer des Befehls ausgeführt werden, zu dem sie gehören.)

  3. Sobald der erste Befehl ausgeführt wurde, wird die Shell ausgeführt echo "Hi". Die Standardausgabe befindet sich dort, wo sie vor dem ersten Befehl war.

AlexP
quelle
Gibt es einen Grund, warum 2a vor 2b ausgeführt wird? (von rechts nach links)
Joey Pabalinas
5
Umleitungen müssen vor dem Befehl ausgeführt werden, für den sie gelten, nein? Wie könnten sie anders arbeiten?
AlexP
Aha, ich hätte es nie so gesehen! Die ersten beiden Antworten sind großartig. Ich gebe es ein bisschen, bevor ich mich für eines entscheide, aber ich schätze beide Erklärungen!
Joey Pabalinas
Leider kann ich nur eine Antwort auswählen, daher gehe ich zu dieser, da sie etwas weniger technisch ist und daher meiner Meinung nach auch den weniger technisch versierten Benutzern helfen kann. Allerdings hatte @DepressedDaniel eine ebenso große Antwort hier , dass Angebote eine genauere Erklärung.
Joey Pabalinas
14

Um nicht eine Unterschale bzw. Unterprozess zu verwenden, wenn das Ausgangssignal einer Verbindung Liste {}geleitet wird >, speichert der Schale die STDOUT- Deskriptor vor der Verbindung Liste ausgeführt wird und stellt sie wieder her , nachdem. Daher exec >wirkt sich die Liste in der Verbundliste nicht über den Punkt hinaus aus, an dem der alte Deskriptor als STDOUT wiederhergestellt wird.

Werfen wir einen Blick auf den relevanten Teil von strace bash -c '{ exec >/dev/null; } >/dev/null; echo hi' 2>&1 | cat -n:

   132  open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
   133  fcntl(1, F_GETFD)                       = 0
   134  fcntl(1, F_DUPFD, 10)                   = 10
   135  fcntl(1, F_GETFD)                       = 0
   136  fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
   137  dup2(3, 1)                              = 1
   138  close(3)                                = 0
   139  open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
   140  fcntl(1, F_GETFD)                       = 0
   141  fcntl(1, F_DUPFD, 10)                   = 11
   142  fcntl(1, F_GETFD)                       = 0
   143  fcntl(11, F_SETFD, FD_CLOEXEC)          = 0
   144  dup2(3, 1)                              = 1
   145  close(3)                                = 0
   146  close(11)                               = 0
   147  dup2(10, 1)                             = 1
   148  fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
   149  close(10)                               = 0

Sie können sehen, wie in Zeile 134 descriptor 1( STDOUT) mindestens auf einen anderen Deskriptor mit Index kopiert wird 10(das ist es, was F_DUPFDfunktioniert; es gibt den niedrigsten verfügbaren Deskriptor ab der angegebenen Nummer zurück, nachdem er auf diesen Deskriptor dupliziert wurde). Sehen Sie auch, wie in Zeile 137 das Ergebnis von open("/dev/null")(descriptor 3) auf descriptor 1( STDOUT) kopiert wird . Schließlich wird online 147der alte STDOUTgespeicherte Deskriptor 10wieder in descriptor 1( STDOUT) kopiert . Der Nettoeffekt besteht darin, die Änderung STDOUTan der Leitung 144(die der inneren entspricht exec >/dev/null) zu isolieren .

DepressedDaniel
quelle
Da FD 1 in Zeile 137 von FD 3 überschrieben wird, warum zeigt Zeile 141 nicht auf / dev / null?
Joey Pabalinas
@JoeyPabalinas Zeile 141 dupliziert FD 1 (dh stdout) zum nächsten verfügbaren Deskriptor nach 10 , was sich als 11 herausstellt, wie Sie im Rückgabewert dieses Systemaufrufs sehen können. 10 ist nur fest in bash programmiert, damit das Speichern der bash-Deskriptoren die einstelligen Deskriptoren nicht beeinträchtigt, mit denen Sie möglicherweise in Ihrem Skript arbeiten exec.
DepressedDaniel
Also bezieht sich fcntl (1, F_DUPFD, 10) immer auf STDOUT, egal wohin FD 1 gerade zeigt?
Joey Pabalinas
@ JoeyPabalinas Nicht sicher, was Ihre Frage ist. FD 1 IST STDOUT. Sie sind das gleiche.
DepressedDaniel
Volle Strace-Ausgabe zu meinem ursprünglichen Beitrag hinzugefügt.
Joey Pabalinas
8

Der Unterschied zwischen { exec >/dev/null; } >/dev/null; echo "Hi"und { exec >/dev/null; }; echo "Hi"besteht darin, dass die doppelte Umleitung ausgeführt wird, dup2(10, 1);bevor fd 10 geschlossen wird, bei dem es sich um die Kopie des Originals handelt stdout, bevor der nächste Befehl ausgeführt wird ( echo).

Dies geschieht auf diese Weise, weil die äußere Umleitung die innere Umleitung überlagert. Deshalb kopiert es das Original- stdoutFD zurück, sobald es fertig ist.

Julie Pelletier
quelle
+1 zur einfachen Erklärung des Unterschieds. In der Antwort von AlexP fehlt diese Erklärung.
Kamil Maciorowski