In welcher Reihenfolge führt die Shell Befehle und Stream-Umleitungen aus?

32

Ich habe heute versucht, beide stdoutund stderrzu einer Datei umzuleiten , und dabei bin ich auf Folgendes gestoßen:

<command> > file.txt 2>&1

Diese leitet offenbar stderrauf den stdoutersten und dann die resultierende stdoutwird umgeleitet file.txt.

Warum ist die Bestellung jedoch nicht gültig <command> 2>&1 > file.txt? Man würde dies natürlich so lesen (unter der Annahme, dass der Befehl von links nach rechts ausgeführt wird), dass er zuerst ausgeführt wird, stderrdass er umgeleitet wird stdoutund dann, dass stdouter geschrieben wird file.txt. Die oben genannten Optionen leiten jedoch nur stderrzum Bildschirm weiter.

Wie interpretiert die Shell beide Befehle?

Herznetz trainieren
quelle
7
TLCL sagt dies: "Zuerst leiten wir die Standardausgabe in die Datei um, und dann leiten wir den Dateideskriptor 2 (Standardfehler) in den Dateideskriptor 1 (Standardausgabe) um." Und "Beachten Sie, dass die Reihenfolge der Umleitungen signifikant ist. Die Umleitung des Standardfehlers." muss immer nach der Umleitung der Standardausgabe auftreten oder es funktioniert nicht "
Zanna
@Zanna: Ja, meine Frage kam genau von der Lektüre in TLCL! :) Ich bin daran interessiert zu wissen, warum es nicht funktioniert, dh wie die Shell Befehle im Allgemeinen interpretiert.
Trainiere Heartnet
Nun, in Ihrer Frage sagen Sie das Gegenteil: "Dies leitet anscheinend stderr zuerst an stdout weiter ..." - was ich von TLCL verstehe, ist, dass die Shell stdout an die Datei und dann stderr an stdout (dh an die Datei) sendet . Meine Interpretation ist, dass, wenn Sie stderr an stdout senden, es im Terminal angezeigt wird und die nachfolgende Umleitung von stdout stderr nicht einschließt (dh die Umleitung von stderr zum Bildschirm ist abgeschlossen, bevor die Umleitung von stdout erfolgt?)
Zanna
7
Ich weiß, dass dies eine altmodische Aussage ist, aber Ihre Shell enthält ein Handbuch, das diese Dinge erklärt - z. B. die Umleitung im bashHandbuch . Weiterleitungen sind übrigens keine Befehle.
Reinier Post
Der Befehl kann nicht ausgeführt werden, bevor die Umleitungen eingerichtet wurden: Wenn der execvSystemaufruf -family aufgerufen wird, um den Unterprozess tatsächlich an den zu startenden Befehl weiterzuleiten, ist die Shell nicht mehr in der Schleife (in diesem Prozess wird kein Code mehr ausgeführt) ) und hat keine Möglichkeit zu kontrollieren, was von diesem Zeitpunkt an geschieht; Umleitungen müssen daher alle ausgeführt werden, bevor die Ausführung beginnt, während auf der Shell eine Kopie von sich selbst in dem Prozess ausgeführt wird, in dem sie fork()Ihren Befehl
Charles Duffy

Antworten:

41

Wenn Sie <command> 2>&1 > file.txtstderr ausführen 2>&1, wird Ihr Terminal umgeleitet . Danach wird stdout von in die Datei umgeleitet >, aber stderr wird nicht mit dieser umgeleitet, bleibt also als Terminalausgabe.

Mit <command> > file.txt 2>&1stdout wird zuerst in die Datei umgeleitet >, dann wird 2>&1stderr dorthin umgeleitet, wohin stdout geht, welches die Datei ist.

Es mag zunächst als nicht intuitiv erscheinen, aber wenn Sie sich die Umleitungen auf diese Weise vorstellen und sich daran erinnern, dass sie von links nach rechts verarbeitet werden, ist dies viel sinnvoller.

Arronisch
quelle
Es ist sinnvoll, wenn Sie in Form von Dateideskriptoren und "dup / fdreopen" -Aufrufen denken, die in der Reihenfolge von links nach rechts ausgeführt werden
Mark K Cowan,
20

Es könnte sinnvoll sein, wenn Sie es ausfindig machen.

Am Anfang gehen stderr und stdout auf dasselbe (normalerweise das Terminal, das ich hier nenne pts):

fd/0 -> pts
fd/1 -> pts
fd/2 -> pts

Ich beziehe mich hier auf stdin, stdout und stderr durch ihre Dateideskriptornummern : sie sind Dateideskriptoren 0, 1 bzw. 2.

In der ersten Umleitung haben wir > file.txtund 2>&1.

So:

  1. > file.txt: fd/1geht jetzt zu file.txt. Mit >, 1ist der implizite Dateideskriptor, wenn nichts angegeben ist. Dies ist also 1>file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
  2. 2>&1: fd/2Jetzt geht es dorthin, wohin fd/1 gerade :

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> file.txt

Andererseits mit 2>&1 > file.txtumgekehrter Reihenfolge:

  1. 2>&1: fd/2Jetzt geht es dorthin, wohin es fd/1gerade geht, was bedeutet, dass sich nichts ändert:

    fd/0 -> pts
    fd/1 -> pts
    fd/2 -> pts
  2. > file.txt: fd/1jetzt geht an file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts

Der wichtige Punkt ist, dass Umleitung nicht bedeutet, dass der umgeleitete Dateideskriptor allen zukünftigen Änderungen am Zieldateideskriptor folgt. es wird nur den aktuellen Zustand annehmen .

muru
quelle
Danke, das scheint eine natürlichere Erklärung zu sein! :) Du hast 2.im zweiten Teil einen kleinen Tippfehler gemacht ; fd/1 -> file.txtund nicht fd/2 -> file.txt.
Train Heartnet
11

Ich denke, es hilft zu denken, dass die Shell zuerst die Umleitung auf der linken Seite einrichtet und sie abschließt , bevor die nächste Umleitung eingerichtet wird.

Die Linux Command Line von William Shotts sagt

Zuerst leiten wir die Standardausgabe in die Datei um und dann leiten wir den Dateideskriptor 2 (Standardfehler) auf den Dateideskriptor 1 (Standardausgabe) um.

das macht aber dann sinn

Beachten Sie, dass die Reihenfolge der Umleitungen wichtig ist. Die Umleitung des Standardfehlers muss immer nach der Umleitung der Standardausgabe erfolgen, sonst funktioniert sie nicht

Tatsächlich können wir stdout nach stderr umleiten, nachdem stderr in eine Datei mit dem gleichen Effekt umgeleitet wurde

$ uname -r 2>/dev/null 1>&2
$ 

In command > file 2>&1diesem Fall sendet die Shell stdout an eine Datei und sendet dann stderr an stdout (das an eine Datei gesendet wird). Während in command 2>&1 > fileder Shell zuerst stderr zu stdout umleitet (dh im Terminal anzeigt, wohin stdout normalerweise geht) und anschließend stdout in die Datei umleitet. TLCL führt in die Irre, dass wir stdout zuerst umleiten müssen: da wir stderr zuerst in eine Datei umleiten und dann stdout dorthin senden können. Was wir nicht tun können, ist stdout nach stderr umzuleiten oder umgekehrt, bevor in eine Datei umgeleitet wird. Ein anderes Beispiel

$ strace uname -r 1>&2 2> /dev/null 
4.8.0-30-generic

Wir denken vielleicht, dies würde stdout an die gleiche Stelle wie stderr bringen, aber das tut es nicht, es leitet stdout zuerst an stderr (den Bildschirm) weiter und leitet dann nur stderr weiter, als wenn wir es andersherum versucht hätten ...

Ich hoffe das bringt ein bisschen Licht ...

Zanna
quelle
So viel beredter!
Arronical
Ah, jetzt verstehe ich! Vielen Dank, @Zanna und @Arronical! Ich beginne gerade mit meiner Kommandozeilenreise. :)
Train Heartnet
@TrainHeartnet es ist ein Vergnügen! Hoffe es gefällt euch genauso gut wie mir: D
Zanna
@ Zanna: In der Tat bin ich! : D
Train Heartnet
2
@TrainHeartnet Keine Sorge, eine Welt voller Frust und Freude erwartet Sie!
Arronical
10

Sie haben bereits einige sehr gute Antworten erhalten. Lassen Sie mich jedoch betonen, dass es sich um zwei verschiedene Konzepte handelt, deren Verständnis enorm hilft:

Hintergrund: Dateideskriptor vs. Dateitabelle

Ihr Dateideskriptor ist nur eine Zahl 0 ... n, der Index in der Dateideskriptortabelle in Ihrem Prozess. Gemäß Konvention ist STDIN = 0, STDOUT = 1, STDERR = 2 (beachten Sie, dass die Begriffe STDINusw. hier nur Symbole / Makros sind , die gemäß Konvention in einigen Programmiersprachen und Manpages verwendet werden; es gibt kein aktuelles "Objekt" mit dem Namen STDIN; z der Zweck dieser Diskussion, STDIN ist 0, etc.).

Diese Dateideskriptortabelle selbst enthält keinerlei Informationen darüber, was die eigentliche Datei ist. Stattdessen enthält es einen Zeiger auf eine andere Dateitabelle. Letzteres enthält Informationen zu einer tatsächlichen physischen Datei (oder einem Blockgerät oder einer Pipe oder was auch immer Linux über den Dateimechanismus adressieren kann) und weitere Informationen (z. B. ob es zum Lesen oder Schreiben dient).

Wenn Sie also >oder <in Ihrer Shell verwenden, ersetzen Sie einfach den Zeiger des jeweiligen Dateideskriptors, um auf etwas anderes zu verweisen. Die Syntax zeigt 2>&1einfach Deskriptor 2 auf 1 Punkt. > file.txtöffnet sich einfach file.txtzum Schreiben und lässt STDOUT (Datei-Decsriptor 1) darauf verweisen.

Es gibt andere Extras, z. B 2>(xxx) .: Erstellen eines neuen Prozesses xxx, Erstellen einer Pipe, Verbinden des Dateideskriptors 0 des neuen Prozesses mit dem Leseende der Pipe und Verbinden des Dateideskriptors 2 des ursprünglichen Prozesses mit dem Schreibende der Pipe Rohr).

Dies ist auch die Grundlage für "Datei-Handle-Magie" in einer anderen Software als Ihrer Shell. Beispielsweise können Sie in Ihrem Perl-Skript dupden STDOUT-Dateideskriptor in einen anderen (temporären) Deskriptor zerlegen und dann STDOUT erneut in einer neu erstellten temporären Datei öffnen. Ab diesem Zeitpunkt werden alle STDOUT-Ausgaben Ihres eigenen Perl-Skripts und alle system()Aufrufe dieses Skripts in dieser temporären Datei gespeichert. Wenn Sie fertig sind, können Sie dupIhr STDOUT auf den temporären Deskriptor zurücksetzen, in dem Sie es gespeichert haben, und außerdem ist alles wie zuvor. Sie können in der Zwischenzeit sogar in diesen temporären Deskriptor schreiben. Während also Ihre aktuelle STDOUT-Ausgabe in die temporäre Datei verschoben wird, können Sie tatsächlich noch Daten in das echte STDOUT ausgeben (normalerweise der Benutzer).

Antworten

So wenden Sie die oben angegebenen Hintergrundinformationen auf Ihre Frage an:

In welcher Reihenfolge führt die Shell Befehle und Stream-Umleitungen aus?

Links nach rechts.

<command> > file.txt 2>&1

  1. fork aus einem neuen Prozess.
  2. Öffnen file.txtund speichern Sie den Zeiger im Dateideskriptor 1 (STDOUT).
  3. Zeigen Sie mit STDERR (Dateideskriptor 2) auf das, worauf der FD 1 gerade zeigt (was file.txtnatürlich wieder der bereits geöffnete ist ).
  4. exec das <command>

Hierdurch wird stderr anscheinend zuerst an stdout umgeleitet, und dann wird die resultierende stdout an file.txt umgeleitet.

Dies wäre sinnvoll, wenn es nur eine Tabelle gäbe , aber wie oben erläutert, gibt es zwei. Dateideskriptoren verweisen nicht rekursiv aufeinander. Es macht keinen Sinn, "STDERR zu STDOUT umleiten". Der richtige Gedanke ist "point STDERR to wherever STDOUT points". Wenn Sie STDOUT später ändern, bleibt STDERR dort, wo es ist. Weitere Änderungen an STDOUT werden nicht auf magische Weise übernommen.

AnoE
quelle
Upvoting für das "file handle magic" Bit - obwohl es nicht direkt die Frage beantwortet, habe ich heute etwas Neues gelernt ...
Floris
3

Die Reihenfolge ist von links nach rechts. Bashs Handbuch hat bereits Ihre Fragen beantwortet. Zitat aus dem REDIRECTIONAbschnitt des Handbuchs:

   Redirections  are  processed  in  the
   order they appear, from left to right.

und ein paar Zeilen später:

   Note that the order of redirections is signifi
   cant.  For example, the command

          ls > dirlist 2>&1

   directs both standard output and standard error
   to the file dirlist, while the command

          ls 2>&1 > dirlist

   directs   only  the  standard  output  to  file
   dirlist, because the standard error was  dupli
   cated from the standard output before the stan
   dard output was redirected to dirlist.

Es ist wichtig zu beachten, dass die Umleitung zuerst aufgelöst wird, bevor Befehle ausgeführt werden! Siehe https://askubuntu.com/a/728386/295286

Sergiy Kolodyazhnyy
quelle
3

Es ist immer von links nach rechts ... außer wenn

Genau wie in Mathe verfahren wir von links nach rechts, mit der Ausnahme, dass die Multiplikation und Division vor der Addition und Subtraktion erfolgt, mit der Ausnahme, dass Operationen in Klammern (+ -) vor der Multiplikation und Division erfolgen.

Gemäß der Anleitung für Bash-Anfänger hier (Anleitung für Bash-Anfänger ) gibt es 8 Hierarchiestufen für das, was zuerst kommt (von links nach rechts):

  1. Klammererweiterung "{}"
  2. Tilde-Erweiterung "~"
  3. Shell-Parameter und Variablenausdruck "$"
  4. Befehlsersetzung "$ (Befehl)"
  5. Arithmetischer Ausdruck "$ ((EXPRESSION))"
  6. Prozessersetzung wovon wir hier sprechen "<(LIST)" oder "> (LIST)"
  7. Wortteilung "'<Leerzeichen> <Tab> <Zeilenumbruch>'"
  8. Dateinamenerweiterung "*", "?" Usw.

Es ist also immer von links nach rechts ... außer wenn ...

WinEunuuchs2Unix
quelle
1
Um es klar auszudrücken: Prozessersetzung und Umleitung sind zwei unabhängige Vorgänge. Sie können eins ohne das andere tun. Prozessersetzung bedeutet nicht, dass bash beschließt, die Reihenfolge zu ändern, in der die Umleitung verarbeitet wird
muru