È un refuso nella sezione di reindirizzamento del manuale di Bash?


13
Note that the order of redirections is significant.  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 duplicated from the standard output before the standard
   output was redirected to dirlist.

Ora, quest'ultima parte mi confonde. In tal caso, qualsiasi errore standard verrebbe stampato sul terminale e qualsiasi STDOUT andrebbe nel file dirlist. Questo è ciò che accadrebbe, ma non è così che capisco il manuale.

Sembra che dovrebbe dire "perché l'errore standard è stato duplicato dall'output standard DOPO che l'output standard è stato reindirizzato alla directory". Se STDERR è stato inviato a STDOUT prima che STDOUT fosse indirizzato a un file, il file non contiene STDOUT E STDERR?

Qualcuno può chiarire questo per me? È solo scarsa comprensione della lettura da parte mia? L'uso della parola duplicazione mi sembra un po 'strano in questo contesto. Forse questo mi sta gettando.



1
Un classico caso di operazioni di confusione che sono "per valore" vs "per riferimento". Quando si duplica un descrittore di file, si tratta di un'operazione per valore . Nella programmazione, dopo che a = 1; b = a; a = 2ti aspetti a == 2 && b == 1di essere vero. Il reindirizzamento 2>&1è simile b = aall'assegnazione: è per valore, non per riferimento. 2>&1non unisce il descrittore di file 2 al descrittore di file 1 per tutta l'eternità - sono ancora 2 descrittori di file distinti, che capita di puntare allo stesso file.
jw013,

Risposte:


23

La duplicazione è davvero la parte importante qui.

Vediamo dove andranno i descrittori di file prima del reindirizzamento. Questo è normalmente il terminale corrente, ad es .:

STDOUT ---> /dev/pts/1
STDERR ---> /dev/pts/1

Ora, se chiamiamo ls -lsenza reindirizzamento, i messaggi di output e di errore vanno al mio terminale sotto /dev/pts/1.

Se prima reindirizziamo STDOUTa un file ( ls -l > dirlist), è simile al seguente:

STDOUT ---> /home/bon/dirlist
STDERR ---> /dev/pts/1

Quando reindirizziamo quindiSTDERR a un duplicato del STDOUTdescrittore di file ( ls -l > dirlist 2>&1), si STDERRpassa a un duplicato di /home/bon/dirlist:

STDOUT ---> /home/bon/dirlist
STDERR ---> /home/bon/dirlist

Se per prima cosa reindirizzassimo STDERRa un duplicato del STDOUTdescrittore di file ( ls -l 2>&1):

STDOUT ---> /dev/pts/1
STDERR ---> /dev/pts/1

e quindi STDOUT su un file ( ls -l 2>&1 > dirlist), otterremmo questo:

STDOUT ---> /home/bon/dirlist
STDERR ---> /dev/pts/1

Qui, STDERRsta ancora andando al terminal.

Vedi, l'ordine nella pagina man è corretto.


Test del reindirizzamento

Ora puoi provarlo tu stesso. Usando ls -l /proc/$$/fd/, vedi dove STDOUT(con fd 1) e STDERR(con fd 2), stanno andando per il processo corrente:

$ ls -l /proc/$$/fd/
total 0
lrwx------ 1 bon bon 64 Jul 24 18:19 0 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 18:19 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 07:41 2 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 18:19 255 -> /dev/pts/1

Creiamo un piccolo script shell che mostra dove sono puntati i descrittori dei tuoi file. In questo modo, otteniamo sempre lo stato durante la chiamata ls, incluso qualsiasi reindirizzamento dalla shell chiamante.

$ cat > lookfd.sh
#!/bin/sh
ls -l /proc/$$/fd/
^D
$ chmod +x lookfd.sh

(Con CtrlD, si invia un end-of-file e quindi si interrompe la catlettura del comando STDIN.)

Ora chiama questo script con varie combinazioni di reindirizzamento:

$ ./lookfd.sh 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:08 0 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:08 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:08 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:08 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh > foo.out
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:10 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:10 1 -> /home/bon/foo.out
lrwx------ 1 bon bon 64 Jul 24 19:10 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:10 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh 2>&1 > foo.out
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:10 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:10 1 -> /home/bon/foo.out
lrwx------ 1 bon bon 64 Jul 24 19:10 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:10 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh > foo.out 2>&1
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:11 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:11 1 -> /home/bon/foo.out
l-wx------ 1 bon bon 64 Jul 24 19:11 2 -> /home/bon/foo.out
lr-x------ 1 bon bon 64 Jul 24 19:11 255 -> /home/bon/lookfd.sh

Puoi vedere che i descrittori di file 1 (per STDOUT) e 2 (per STDERR) variano. Per divertimento, puoi anche reindirizzare STDINe vedere il risultato:

$ ./lookfd.sh < /dev/zero
total 0
lr-x------ 1 bon bon 64 Jul 24 19:18 0 -> /dev/zero
lrwx------ 1 bon bon 64 Jul 24 19:18 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:18 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:18 255 -> /home/bon/lookfd.sh

(Domanda lasciata al lettore: dove punta il descrittore di file 255? ;-))


+1 - risposta eccellente. Esempi estremamente ben scritti e fantastici. Grazie!!!
slm

Vedo, penso che il mio malinteso fosse che il reindirizzamento sarebbe stato persistente per tutti i seguenti comandi, in modo che qualsiasi STDERR per il resto della linea andasse a STDOUT.
Gregg Leventhal,

2

No, il manuale è giusto.

Se all'inizio 1 indica il terminale e 2 anche il terminale, quindi:

command  2>&1   1>somewhere

la valutazione del reindirizzamento avverrà da sinistra a destra.

Quindi PRIMA valuterà 2>&1, e quindi PRIMA copia ciò che fd indicava 1(cioè il descrittore di file the terminal, di solito / dev / tty) in fd 2.

Quindi a quel punto fd 2ora indica dove fd indicava 1( the terminal)

E POI valuta la 1>somewhereparte e quindi copierà il descrittore di file di somewherein fd 1(quindi a quel punto, fd 1ora punta a somewheree fd 2punta ancora a the terminal)

Quindi stampa 1 in "da qualche parte" e 2 nel terminale, poiché 2 è stato duplicato da 1 PRIMA che 1 sia stato modificato.

L'altro ordine:

command  1>somewhere 2>&1

reindirizzerà prima fd 1a somewhere, quindi copierà lo stesso riferimento in fd 2, quindi alla fine 2 punta anche a somewhere. Ma non sono "collegati" da ora in poi. Ciascuno può ancora essere reindirizzato separatamente.

ex:

command  1>somewhere 2>&1
exec 2>/dev/null

Alla fine di quello, fd 1indica somewheree fd 2è diretto a/dev/null

I nomi usuali di fd 1è STDOUT (output standard) e il nome abituale di fd 2è STDERR (errore standard, poiché viene comunemente utilizzato per visualizzare gli errori senza interferire con STDOUT)


@ Michael-mrozek: grazie per la modifica, ma insisto per dire "copia" invece di "duplicato" in quanto "duplicato" potrebbe indurre a credere che d'ora in poi entrambi sono "la stessa cosa", il che non è vero. es:: cmd 1>somewhere 2>&1 ; exec 2>/dev/nulldopo il exec, solo 2 sono stati reindirizzati a / dev / null (1 sta ancora andando a "da qualche parte"). Ho bisogno di aiuto per trovare un modo per dire "cosa indica 1" invece di "fd 1", tuttavia ... poiché anche questo è fonte di confusione ...
Olivier Dulac

1
Non sono sicuro di cosa intendi; sei tu quello che l'ha cambiato da "copia" a "duplicato". Tutto quello che ho fatto è stato capitalizzare e formattare le cose, non ho cambiato parola
Michael Mrozek

doh ... ^^ scusa. E ho modificato di nuovo per riformulare per rendere più preciso ciò che viene copiato in ciò che ^^
Olivier Dulac,

1

Penso che la parte confusa qui sia la cattiva comprensione che il reindirizzamento di stderr a stdout in realtà collega i due flussi.

Un'idea perfettamente ragionevole ma ciò che accade quando scrivi 2>&1è stderr prende uno sguardo a ciò che stdout sta scrivendo e scrive nello stesso posto stesso. Pertanto, se successivamente dici a stdout di andare a scrivere altrove, non ha alcun effetto sulla destinazione di stderr che è già stata spostata.

Penso che sia un po 'controintuitivo, ma è così che funziona. Imposta dove vuoi scrivere per primo, poi dì a tutti "copiami". Spero che chiarisca ...


0

DUPLICAZIONE...

è importante, ma piuttosto nel senso che è fonte di molta confusione . È davvero abbastanza semplice. Questa risposta è solo un'illustrazione "radicale".

La risposta accettata è buona, ma troppo lunga e sottolinea la "duplicazione".

La Q finisce saggiamente con:

L' uso della parola duplicazione mi sembra un po 'strano in questo contesto. Forse questo mi sta gettando.

Uso la notazione bash e definisco le variabili "uno" e "due" come filehandle "1" e "2". L'operatore di reindirizzamento (output) >è un compito =. &e $significa "valore" di.

Gli esempi man bash (con l'aggiunta di "1" predefinito)

ls 1>dirlist 2>&1      # both to dirlist
ls 2>&1 1>dirlist      # 1 to dirlist, 2 stays on tty/screen 

diventare:

one=dirlist  two=$one

e

two=$one   one=dirlist

E anche questo non è automatico per me, e alcuni altri immagino. La prima riga ti lascia con $oneed $twoentrambi contenenti "dirlist". Ovviamente.

La seconda riga inizia con un compito inutile. Entrambi iniziano per definizione con "TTY" (un po 'simbolico) come direzione ; nessun valore viene modificato da questo compito e con le variabili come per i filehandle, nulla è magicamente collegato. La variabile twonon è influenzata da quanto segue one=dirlist. Ovviamente no.

Sombody qui (6 anni fa) ha suggerito di "indicare" invece di "copiare" o "duplicare", e poi ha capito: anche questo sarebbe confuso.

Questa semantica di duplicazione o puntatore non è nemmeno necessaria. Forse è la e commerciale che richiede più attenzione. Il "valore di" operatore / token / qualunque cosa.

Se - e solo se - stai cercando un modo per ottenere un numero di lavoro sorprendente sulla tua console , quindi un messaggio "fatto" più come bonus un file chiamato "2", allora vai:

ls 1>2& 2>/dev/null

Legge naturalmente come " copia" / "duplicato" da 1 a 2, quindi entrambi insieme a null . Ma l'idea è sbagliata e anche la sintassi. (ma nessun errore di sintassi, è valido)

Il modo giusto per pianificarlo è reindirizzare uno dei due su null, quindi reindirizzare l'ALTRO nella stessa posizione:

ls 1>/dev/null 2>&1
# or 
ls 2>/dev/null 1>&2

(il primo "1" può essere lasciato da parte)

(OK l'acc. A non è troppo lungo, ma è troppo di un elenco - o: ottima visualizzazione, spiegazione non così buona)

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.