W jakiej kolejności powłoka wykonuje polecenia i przekierowuje strumień?

32

Starałem się przekierować zarówno stdouti stderrdo pliku dzisiaj, i natknąłem się na to:

<command> > file.txt 2>&1

To najwyraźniej przekierowuje stderrdo stdoutpierwszego, a następnie wynikowy stdoutprzekierowuje do file.txt.

Dlaczego jednak zamówienie nie jest <command> 2>&1 > file.txt? Można by to oczywiście odczytać jako (zakładając wykonanie od lewej do prawej) najpierw wykonywane polecenie, stderrprzekierowanie do, stdouta następnie stdoutzapisanie do wynikowego file.txt. Ale powyższe tylko przekierowuje stderrdo ekranu.

Jak powłoka interpretuje oba polecenia?

Pociąg Heartnet
źródło
7
TLCL mówi: „Najpierw przekierowujemy standardowe wyjście do pliku, a następnie przekierowujemy deskryptor pliku 2 (błąd standardowy) do deskryptora pierwszego (standardowe wyjście)” i „Zauważ, że kolejność przekierowań jest znacząca. Przekierowanie standardowego błędu musi zawsze występować po przekierowaniu standardowego wyjścia lub nie działa ”
Zanna
@Zanna: Tak, moje pytanie dokładnie pochodzi z przeczytania tego w TLCL! :) Chciałbym wiedzieć, dlaczego to nie zadziała, tj. Jak ogólnie powłoka interpretuje polecenia.
Pociąg Heartnet,
Cóż, w swoim pytaniu mówisz odwrotnie: „To najwyraźniej przekierowuje stderr na stdout najpierw ...” - rozumiem z TLCL, że powłoka wysyła stdout do pliku, a następnie stderr do stdout (tj. Do pliku). Moja interpretacja jest taka, że ​​jeśli wyślesz stderr na stdout, to zostanie on wyświetlony w terminalu, a kolejne przekierowanie stdout nie obejmie stderr (tj. Przekierowanie stderr na ekran zostanie zakończone zanim nastąpi przekierowanie stdout?)
Zanna
7
Wiem, że jest to staromodna rzecz do powiedzenia, ale twoja powłoka jest wyposażona w instrukcję, która wyjaśnia te rzeczy - np. Przekierowanie w bashinstrukcji . Nawiasem mówiąc, przekierowania nie są poleceniami.
Reinier Post
Komendy nie można wykonać przed skonfigurowaniem przekierowań: gdy execvwywoływana jest funkcja -family syscall w celu przekazania podprocesu uruchamianej komendzie, powłoka jest poza pętlą (kod nie wykonuje już kodu w tym procesie ) i nie ma możliwości kontrolowania tego, co dzieje się od tego momentu; dlatego wszystkie przekierowania muszą zostać wykonane przed rozpoczęciem wykonywania, podczas gdy powłoka ma kopię samego siebie uruchomioną w procesie, w fork()którym uruchomiono polecenie.
Charles Duffy,

Odpowiedzi:

41

Kiedy uruchomisz <command> 2>&1 > file.txtstderr, zostaniesz przekierowany 2>&1do tego, dokąd aktualnie idzie stdout, twój terminal. Następnie stdout jest przekierowywany do pliku przez >, ale stderr nie jest z nim przekierowywany, więc pozostaje jako wyjście terminala.

Z <command> > file.txt 2>&1stdout jest najpierw przekierowywany do pliku przez >, a następnie 2>&1przekierowuje stderr do dokąd zmierza stdout, czyli do pliku.

Na początku może wydawać się to sprzeczne z intuicją, ale kiedy pomyślisz o przekierowaniach w ten sposób i pamiętasz, że są one przetwarzane od lewej do prawej, ma to o wiele większy sens.

Arroniczny
źródło
Ma to sens, jeśli myślisz w kategoriach deskryptorów plików i wywołań „dup / fdreopen” wykonywanych w kolejności od lewej do prawej
Mark K Cowan
20

Może to mieć sens, jeśli go wyśledzisz.

Na początku stderr i stdout idą do tego samego (zazwyczaj terminalu, który tu nazywam pts):

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

Mam tu na myśli stdin, stdout i stderr po ich numerach deskryptorów plików : są to odpowiednio deskryptory plików 0, 1 i 2.

Teraz w pierwszym zestawie przekierowań mamy > file.txti 2>&1.

Więc:

  1. > file.txt: fd/1teraz idzie do file.txt. Z >, 1to domyślny deskryptor pliku, gdy nic nie jest określone, więc jest to 1>file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
  2. 2>&1: fd/2Teraz idzie tam, gdzie fd/1 obecnie idzie:

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

Z drugiej strony, przy 2>&1 > file.txtodwróceniu kolejności:

  1. 2>&1: fd/2teraz trafia do dowolnego miejsca fd/1, co oznacza, że ​​nic się nie zmienia:

    fd/0 -> pts
    fd/1 -> pts
    fd/2 -> pts
  2. > file.txt: fd/1teraz idzie do file.txt:

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

Ważne jest to, że przekierowanie nie oznacza, że ​​przekierowany deskryptor pliku będzie śledził wszystkie przyszłe zmiany deskryptora pliku docelowego; przybierze tylko aktualny stan.

muru
źródło
Dziękuję, to wydaje się bardziej naturalne wytłumaczenie! :) Zrobiłeś lekką literówkę w 2.drugiej części; fd/1 -> file.txti nie fd/2 -> file.txt.
Pociąg Heartnet,
11

Myślę, że to pomaga myśleć, że powłoka najpierw skonfiguruje przekierowanie po lewej stronie i ukończy go przed skonfigurowaniem następnego przekierowania.

Wiersz poleceń Linuksa autorstwa Williama Shottsa mówi

Najpierw przekierowujemy standardowe wyjście do pliku, a następnie przekierowujemy deskryptor pliku 2 (błąd standardowy) do pierwszego deskryptora pliku (standardowe wyjście)

to ma sens, ale wtedy

Zauważ, że kolejność przekierowań jest znacząca. Przekierowanie standardowego błędu musi zawsze nastąpić po przekierowaniu standardowego wyjścia lub inaczej nie działa

ale w rzeczywistości możemy przekierować stdout na stderr po przekierowaniu stderr do pliku z tym samym efektem

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

Tak więc w command > file 2>&1powłoka wysyła stdout do pliku, a następnie wysyła stderr do stdout (który jest wysyłany do pliku). Podczas gdy w command 2>&1 > filepowłoce najpierw przekierowuje stderr na stdout (tzn. Wyświetla go w terminalu, gdzie normalnie idzie stdout), a następnie przekierowuje stdout do pliku. TLCL wprowadza w błąd mówiąc, że najpierw musimy przekierować stdout: ponieważ możemy najpierw przekierować stderr do pliku, a następnie wysłać do niego stdout. To, czego nie możemy zrobić, to przekierowanie stdout na stderr lub odwrotnie przed przekierowaniem do pliku. Inny przykład

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

Można by pomyśleć, że to usunie stdout w to samo miejsce co stderr, ale tak nie jest, najpierw przekierowuje stdout do stderr (ekranu), a następnie tylko przekierowuje stderr, tak jak wtedy, gdy próbowaliśmy go odwrotnie ...

Mam nadzieję, że przyniesie to trochę światła ...

Zanna
źródło
O wiele bardziej wymowny!
Arroniczny
Ach, teraz rozumiem! Dziękuję bardzo, @Zanna i @Arronical! Właśnie zaczynałem swoją podróż z linii poleceń. :)
Trenuj Heartnet
@TrainHeartnet to przyjemność! Mam nadzieję, że podoba ci się tak samo jak ja: D
Zanna,
@Zanna: Rzeczywiście, jestem! : D
Train Heartnet,
2
@TrainHeartnet nie martw się, świat frustracji i radości czeka na Ciebie!
Arroniczny
10

Masz już kilka bardzo dobrych odpowiedzi. Chciałbym jednak podkreślić, że w grę wchodzą dwie różne koncepcje, których zrozumienie ogromnie pomaga:

Tło: deskryptor pliku a tabela plików

Twój deskryptor pliku to tylko liczba 0 ... n, która jest indeksem w tabeli deskryptorów plików w twoim procesie. Zgodnie z konwencją STDIN = 0, STDOUT = 1, STDERR = 2 (zwróć uwagę, że STDINtutaj terminy itp. Są tylko symbolami / makrami używanymi w konwencji w niektórych językach programowania i stronach podręcznika , nie ma rzeczywistego „obiektu” o nazwie STDIN; dla celem tej dyskusji, STDIN jest 0 itd.).

Ta tabela deskryptorów plików sama w sobie nie zawiera żadnych informacji na temat rzeczywistego pliku. Zamiast tego zawiera wskaźnik do innej tabeli plików; ten ostatni zawiera informacje o rzeczywistym pliku fizycznym (lub urządzeniu blokowym, potoku lub czymkolwiek innym, co Linux może rozwiązać za pomocą mechanizmu plików) i więcej informacji (tj. czy to do odczytu, czy zapisu).

Więc kiedy używasz >lub <w swojej powłoce, po prostu zastępujesz wskaźnik odpowiedniego deskryptora pliku, aby wskazać coś innego. Składnia 2>&1po prostu wskazuje deskryptor 2 na dowolne 1 punkty. > file.txtpo prostu otwiera się file.txtdo pisania i pozwala STDOUT (plik decsriptor 1) wskazywać na to.

Istnieją inne zalety, np. 2>(xxx) ( Np . : stwórz nowy proces xxx, stwórz potok, podłącz deskryptor pliku 0 nowego procesu do końca odczytu potoku i podłącz deskryptor pliku 2 oryginalnego procesu do końca zapisu rura).

Jest to również podstawa dla „magii obsługi plików” w innym oprogramowaniu niż twoja powłoka. Na przykład możesz w swoim skrypcie Perl duppowiązać deskryptor pliku STDOUT z innym (tymczasowym), a następnie ponownie otworzyć STDOUT dla nowo utworzonego pliku tymczasowego. Od tego momentu wszystkie dane wyjściowe STDOUT z własnego skryptu Perla i wszystkie system()wywołania tego skryptu znajdą się w tym pliku tymczasowym. Po dupzakończeniu możesz przywrócić STDOUT do tymczasowego deskryptora, w którym go zapisałeś, i presto, wszystko jest jak poprzednio. W międzyczasie możesz nawet pisać do tego tymczasowego deskryptora, więc podczas gdy twoje rzeczywiste wyjście STDOUT trafia do pliku tymczasowego, nadal możesz faktycznie wypisywać rzeczy do prawdziwego STDOUT (zwykle użytkownika).

Odpowiedź

Aby zastosować podane powyżej informacje podstawowe do pytania:

W jakiej kolejności powłoka wykonuje polecenia i przekierowuje strumień?

Z lewej na prawą.

<command> > file.txt 2>&1

  1. fork od nowego procesu.
  2. Otwórz file.txti zapisz jego wskaźnik w deskryptorze pliku 1 (STDOUT).
  3. Wskaż STDERR (deskryptor pliku 2) na cokolwiek, na co wskazuje teraz fd 1 (co znowu jest już file.txtoczywiście otwarte ).
  4. exec <command>

To najwyraźniej przekierowuje najpierw stderr na stdout, a następnie wynikowy stdout jest przekierowywany do pliku.txt.

Miałoby to sens, gdyby istniał tylko jeden stół, ale jak wyjaśniono powyżej, są dwa. Deskryptory plików nie wskazują na siebie rekurencyjnie, nie ma sensu myśleć „przekieruj STDERR do STDOUT”. Prawidłowa myśl to „wskaż STDERR tam, gdzie wskazuje STDOUT”. Jeśli zmienisz STDOUT później, STDERR pozostanie tam, gdzie jest, nie magicznie idzie w parze z kolejnymi zmianami w STDOUT.

AnoE
źródło
Upvoting dla „magicznego uchwytu pliku” - choć nie odpowiada bezpośrednio na pytanie, nauczyłem się dziś czegoś nowego…
Floris
3

Kolejność jest od lewej do prawej. Podręcznik Bash zawiera już to, o co prosisz. Cytat z REDIRECTIONczęści instrukcji:

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

i kilka linii później:

   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.

Ważne jest, aby pamiętać, że przekierowanie jest rozwiązywane najpierw przed uruchomieniem jakichkolwiek poleceń! Zobacz https://askubuntu.com/a/728386/295286

Sergiy Kolodyazhnyy
źródło
3

Zawsze jest od lewej do prawej ... z wyjątkiem kiedy

Podobnie jak w matematyce wykonujemy od lewej do prawej, z tym wyjątkiem, że mnożenie i dzielenie odbywa się przed dodawaniem i odejmowaniem, z wyjątkiem operacji wewnątrz nawiasów (+ -), które byłyby wykonywane przed mnożeniem i dzieleniem.

Zgodnie z przewodnikiem Bash dla początkujących tutaj ( Bash Beginners Guide ) istnieje 8 rzędów hierarchii tego, co jest pierwsze (przed od lewej do prawej):

  1. Rozszerzenie nawiasów klamrowych {{}
  2. Rozszerzenie tyldy „~”
  3. Parametr powłoki i wyrażenie zmiennej „$”
  4. Podstawienie polecenia „$ (polecenie)”
  5. Wyrażenie arytmetyczne „$ ((EXPRESSION))”
  6. Przetwarzanie podstaw, o czym tu mówimy „<(LISTA)” lub „> (LISTA”)
  7. Podział słów „” <spacja> <tab> <nowa linia> ”„
  8. Rozszerzenie nazwy pliku „*”, „?” Itp.

Więc zawsze jest od lewej do prawej ... z wyjątkiem sytuacji, gdy ...

WinEunuuchs2Unix
źródło
1
Dla jasności: podstawianie i przekierowywanie procesów to dwie niezależne operacje. Możesz zrobić jedno bez drugiego. Posiadanie substytucji procesu nie oznacza, że ​​bash decyduje się zmienić kolejność, w której przetwarza przekierowanie
muru