In quale ordine la shell esegue i comandi e reindirizza i flussi?


32

Oggi stavo cercando di reindirizzare entrambi stdoute stderrun file, e mi sono imbattuto in questo:

<command> > file.txt 2>&1

Apparentemente questo reindirizza stderrin stdoutprimo luogo, quindi il risultato stdoutviene reindirizzato a file.txt.

Tuttavia, perché non è l'ordine <command> 2>&1 > file.txt? Si potrebbe naturalmente leggere questo come (supponendo l'esecuzione da sinistra a destra) il comando che viene eseguito prima, il stderrreindirizzamento a stdoute quindi, il risultato stdoutviene scritto su file.txt. Ma quanto sopra reindirizza solo stderrallo schermo.

In che modo la shell interpreta entrambi i comandi?


7
TLCL afferma che "Prima reindirizziamo l'output standard sul file, quindi reindirizziamo il descrittore di file 2 (errore standard) al descrittore di file uno (output standard)" e "Si noti che l'ordine dei reindirizzamenti è significativo. Il reindirizzamento dell'errore standard deve sempre verificarsi dopo il reindirizzamento dell'output standard o non funziona "
Zanna,

@Zanna: Sì, la mia domanda è nata proprio dalla lettura in TLCL! :) Mi interessa sapere perché non funzionerebbe, cioè come la shell interpreta i comandi in generale.
Train Heartnet,

Bene, nella tua domanda dici il contrario "Apparentemente reindirizza prima stderr a stdout ..." - quello che ho capito da TLCL, è che la shell invia stdout al file e poi stderr a stdout (cioè al file). La mia interpretazione è che se si invia stderr a stdout, verrà visualizzato nel terminale e il successivo reindirizzamento di stdout non includerà stderr (ovvero il reindirizzamento di stderr sullo schermo viene completato prima che avvenga il reindirizzamento di stdout?)
Zanna

7
So che questa è una cosa vecchio stile da dire, ma la tua shell viene fornita con un manuale che spiega queste cose , ad esempio il reindirizzamento nel bashmanuale . A proposito, i reindirizzamenti non sono comandi.
Reinier Post,

Il comando non può essere eseguito prima che i reindirizzamenti siano impostati: quando execvviene invocato -family syscall per consegnare effettivamente il sottoprocesso all'avvio del comando, la shell è quindi fuori dal ciclo (non ha più codice in esecuzione in quel processo ) e non ha modo di controllare cosa succede da quel momento in poi; tutti i reindirizzamenti devono quindi essere eseguiti prima dell'inizio dell'esecuzione, mentre la shell ha una copia di se stessa in esecuzione nel processo in cui è stato fork()eseguito per eseguire il comando.
Charles Duffy

Risposte:


41

Quando esegui <command> 2>&1 > file.txtstderr viene reindirizzato da 2>&1dove sta andando stdout, il tuo terminale. Successivamente, stdout viene reindirizzato al file da >, ma stderr non viene reindirizzato con esso, quindi rimane come output terminale.

Con <command> > file.txt 2>&1stdout viene prima reindirizzato al file da >, quindi 2>&1reindirizza stderr a dove sta andando stdout, che è il file.

Inizialmente può sembrare controintuitivo, ma quando si pensa ai reindirizzamenti in questo modo e si ricorda che vengono elaborati da sinistra a destra, ha molto più senso.


Ha senso se si pensa in termini di descrittori di file e chiamate "dup / fdreopen" eseguite in ordine da sinistra a destra
Mark K Cowan,

20

Potrebbe avere senso se lo rintracci.

All'inizio, stderr e stdout vanno alla stessa cosa (di solito il terminale, che chiamo qui pts):

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

Mi riferisco a stdin, stdout e stderr dai loro numeri descrittori di file qui: sono rispettivamente descrittori di file 0, 1 e 2.

Ora, nella prima serie di reindirizzamenti, abbiamo > file.txte 2>&1.

Così:

  1. > file.txt: fd/1ora va a file.txt. Con >, 1è il descrittore di file implicito quando non viene specificato nulla, quindi questo è 1>file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
  2. 2>&1: fd/2ora va ovunque vada fd/1 attualmente :

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

D'altra parte, con 2>&1 > file.txt, l'ordine invertito:

  1. 2>&1: fd/2ora va ovunque fd/1vada attualmente, il che significa che nulla cambia:

    fd/0 -> pts
    fd/1 -> pts
    fd/2 -> pts
  2. > file.txt: fd/1ora va a file.txt:

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

Il punto importante è che il reindirizzamento non significa che il descrittore di file reindirizzato seguirà tutte le future modifiche al descrittore di file di destinazione; assumerà solo lo stato attuale .


Grazie, questa sembra una spiegazione più naturale! :) Hai fatto un piccolo errore di battitura nella 2.seconda parte; fd/1 -> file.txte non fd/2 -> file.txt.
Train Heartnet,

11

Penso che aiuta a pensare che la shell imposterà prima il reindirizzamento sulla sinistra e lo completerà prima di impostare il reindirizzamento successivo.

La riga di comando di Linux di William Shotts dice

Prima reindirizziamo l'output standard sul file, quindi reindirizziamo il descrittore di file 2 (errore standard) al descrittore di file uno (output standard)

questo ha senso, ma poi

Si noti che l'ordine dei reindirizzamenti è significativo. Il reindirizzamento dell'errore standard deve sempre verificarsi dopo il reindirizzamento dell'output standard o non funziona

ma in realtà, possiamo reindirizzare stdout su stderr dopo il reindirizzamento di stderr su un file con lo stesso effetto

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

Quindi, in command > file 2>&1, la shell invia stdout a un file, quindi invia stderr a stdout (che viene inviato a un file). Considerando che, nella command 2>&1 > fileshell reindirizza prima stderr su stdout (cioè lo visualizza nel terminale dove normalmente va stdout) e poi reindirizza stdout sul file. TLCL è fuorviante nel dire che dobbiamo prima reindirizzare stdout: poiché possiamo reindirizzare prima stderr su un file e quindi inviare stdout ad esso. Quello che non possiamo fare è reindirizzare stdout su stderr o viceversa prima del reindirizzamento su un file. Un altro esempio

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

Potremmo pensare che questo eliminerebbe stdout nello stesso posto di stderr, ma non lo fa, reindirizza prima stdout a stderr (lo schermo), quindi reindirizza solo stderr, come quando l'abbiamo provato viceversa ...

Spero che questo porti un po 'di luce ...


Molto più eloquente!
Arronical,

Ah, ora capisco! Grazie mille, @Zanna e @Arronical! Ho appena iniziato il mio viaggio dalla riga di comando. :)
Train Heartnet,

@TrainHeartnet è un piacere! Spero che ti piaccia tanto quanto me: D
Zanna,

@Zanna: Anzi, lo sono! : D
Train Heartnet,

2
@TrainHeartnet non preoccuparti, un mondo di frustrazione e gioia ti aspetta!
Arronical,

10

Hai già ricevuto alcune ottime risposte. Vorrei sottolineare tuttavia che ci sono due diversi concetti coinvolti qui, la cui comprensione aiuta enormemente:

Sfondo: descrittore di file e tabella di file

Il descrittore di file è solo un numero 0 ... n, che è l'indice nella tabella dei descrittori di file nel processo. Per convenzione, STDIN = 0, STDOUT = 1, STDERR = 2 (nota che i termini STDINecc. Qui sono solo simboli / macro usati dalla convenzione in alcuni linguaggi di programmazione e pagine man, non esiste un vero "oggetto" chiamato STDIN; per lo scopo di questa discussione, STDIN è 0, ecc.).

La tabella descrittore di file in sé non contiene alcuna informazione su quale sia il file effettivo. Al contrario, contiene un puntatore a una tabella di file diversa; quest'ultimo contiene informazioni su un file fisico reale (o dispositivo a blocchi, pipe o qualsiasi altra cosa che Linux possa indirizzare tramite il meccanismo dei file) e ulteriori informazioni (vale a dire, sia che si tratti di leggere o scrivere).

Quindi quando usi >o <nella tua shell, sostituisci semplicemente il puntatore del rispettivo descrittore di file per indicare qualcos'altro. La sintassi 2>&1indica semplicemente il descrittore 2 ovunque 1 punti. > file.txtsi apre semplicemente file.txtper la scrittura e consente a STDOUT (decsriptor file 1) di indicarlo.

Ci sono altri gadget, ad esempio 2>(xxx) (es: creare un nuovo processo in esecuzione xxx, creare una pipe, collegare il descrittore di file 0 del nuovo processo all'estremità di lettura della pipe e collegare il descrittore di file 2 del processo originale alla fine di scrittura del tubo).

Questa è anche la base del "file handle magic" in altri software oltre alla shell. Ad esempio, è possibile, nello script Perl, duplicenziare il descrittore di file STDOUT in un altro (temporaneo), quindi riaprire STDOUT in un file temporaneo appena creato. Da questo punto in poi, tutto l'output di STDOUT dal tuo script Perl e tutte le system()chiamate di quello script finiranno in quel file temporaneo. Al termine, puoi riportare il duptuo STDOUT al descrittore temporaneo in cui lo hai salvato e presto tutto sarà come prima. Puoi anche scrivere a quel descrittore temporaneo nel frattempo, quindi mentre l'output STDOUT effettivo passa al file temporaneo, puoi comunque effettivamente output roba sul vero STDOUT (comunemente, l'utente).

Risposta

Per applicare le informazioni di base fornite sopra alla domanda:

In quale ordine la shell esegue i comandi e reindirizza i flussi?

Da sinistra a destra.

<command> > file.txt 2>&1

  1. fork fuori da un nuovo processo.
  2. Apri file.txte memorizza il suo puntatore nel descrittore di file 1 (STDOUT).
  3. Punta STDERR (descrittore di file 2) su qualunque cosa la fd 1 indichi in questo momento (che di nuovo è file.txtovviamente già aperto ).
  4. exec il <command>

Questo apparentemente reindirizza prima stderr a stdout, quindi lo stdout risultante viene reindirizzato a file.txt.

Ciò avrebbe senso se ci fosse una sola tabella, ma come spiegato sopra ce ne sono due. I descrittori di file non si indicano reciprocamente in modo ricorsivo, non ha senso pensare "reindirizzare STDERR a STDOUT". Il pensiero corretto è "puntare STDERR ovunque punti STDOUT". Se cambi STDOUT in un secondo momento, STDERR rimane dove si trova, non si adatta magicamente a ulteriori modifiche a STDOUT.


Upgrade per il bit "file handle magic" - anche se non risponde direttamente alla domanda che ho imparato qualcosa di nuovo oggi ...
Floris

3

L'ordine è da sinistra a destra. Il manuale di Bash ha già trattato ciò che chiedi. Citazione dalla REDIRECTIONsezione del manuale:

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

e poche righe dopo:

   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.

È importante notare che il reindirizzamento viene risolto prima di eseguire qualsiasi comando! Vedi https://askubuntu.com/a/728386/295286


3

È sempre da sinistra a destra ... tranne quando

Proprio come in matematica facciamo da sinistra a destra tranne che per la moltiplicazione e la divisione prima dell'aggiunta e della sottrazione, tranne che le operazioni tra parentesi (+ -) vengano eseguite prima della moltiplicazione e della divisione.

Secondo la guida per principianti di Bash qui ( Guida per principianti di Bash ) ci sono 8 ordini di gerarchia di ciò che viene prima (prima da sinistra a destra):

  1. Espansione parentesi "{}"
  2. Espansione Tilde "~"
  3. Parametro Shell ed espressione variabile "$"
  4. Sostituzione comando "$ (comando)"
  5. Espressione aritmetica "$ ((EXPRESSION))"
  6. Sostituzione del processo di cosa stiamo parlando qui "<(LIST)" o "> (LIST)"
  7. Suddivisione delle parole "'<spazio> <tab> <newline>'"
  8. Espansione nome file "*", "?", Ecc.

Quindi è sempre da sinistra a destra ... tranne quando ...


1
Per essere chiari: la sostituzione del processo e il reindirizzamento sono due operazioni indipendenti. Puoi farne uno senza l'altro. Avere la sostituzione del processo non significa che bash decida di cambiare l'ordine in cui elabora il reindirizzamento
muru,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.