Nel codice “{exec> / dev / null; }> / dev / null "cosa sta succedendo sotto il cofano?


15

Quando si reindirizza un elenco di comandi che contiene un reindirizzamento exec, exec> / dev / null non sembra essere ancora applicato in seguito, come ad esempio con:

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

Viene stampato "Ciao".

Avevo l'impressione che l' {}elenco dei comandi non fosse considerato una subshell a meno che non exec >/dev/nullfacesse parte di una pipeline, quindi nella mia mente dovrebbe ancora essere applicato all'interno dell'ambiente di shell corrente.

Ora se lo cambi in:

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

non c'è output come previsto; il descrittore di file 1 rimane puntato su / dev / null anche per comandi futuri. Questo è dimostrato dalla riesecuzione:

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

che non darà alcun risultato.

Ho provato a creare una sceneggiatura ea rimediarla, ma non sono ancora sicuro di cosa stia succedendo qui.

Ad ogni punto di questo script cosa sta succedendo al descrittore di file STDOUT?

EDIT: aggiunta del mio output strace:

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

È strano; Non riesco a riprodurre il file close(10). Puoi anche pubblicare l'intero contenuto dello script su cui hai eseguito la strace?
Depresso

@DepressedDaniel Ecco l'intera sceneggiatura e strace: lo script strace
Joey Pabalinas

Hai un ;dopo randagio }, che cambia il significato di > /dev/nullnon applicare alla lista composta {}dopo tutto.
Depresso

@DepressedDaniel Ah, hai perfettamente ragione! Ora l'output è quello che mi aspetto; Grazie per le tue risposte!
Joey Pabalinas,

Risposte:


17

Seguiamo

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

passo dopo passo.

  1. Esistono due comandi:

    un. { exec >/dev/null; } >/dev/null, seguito da

    b. echo "Hi"

    La shell esegue prima il comando (a) e poi il comando (b).

  2. L'esecuzione dei { exec >/dev/null; } >/dev/nullproventi è la seguente:

    un. Innanzitutto, la shell esegue il reindirizzamento >/dev/null e ricorda di annullarlo al termine del comando .

    b. Quindi, la shell viene eseguita { exec >/dev/null; }.

    c. Infine, la shell riporta l'output standard al punto in cui si trovava. (Questo è lo stesso meccanismo di ls -lR /usr/share/fonts >~/FontList.txt- i reindirizzamenti vengono effettuati solo per la durata del comando a cui appartengono.)

  3. Una volta eseguito il primo comando, la shell viene eseguita echo "Hi". L'output standard è ovunque prima del primo comando.


C'è qualche motivo dietro perché 2a viene eseguita prima di 2b? (da destra a sinistra)
Joey Pabalinas il

5
I reindirizzamenti devono essere eseguiti prima del comando a cui si applicano, no? Come potrebbero funzionare diversamente?
AlexP,

Ah, non ci ho mai pensato in quel modo! I primi due sono grandi risposte; dandolo un po 'prima di decidere su uno, ma apprezzo entrambe le spiegazioni!
Joey Pabalinas,

Sfortunatamente posso solo scegliere una risposta, quindi vado con questa poiché è un po 'meno tecnica e quindi penso che sarebbe in grado di aiutare anche gli utenti meno esperti di tecnologia. Tuttavia @DepressedDaniel ha avuto una risposta altrettanto valida qui che offre una spiegazione più approfondita.
Joey Pabalinas,

14

Per non utilizzare una sub-shell o un processo secondario, quando {}viene inviato l'output di un elenco composto >, la shell salva il descrittore STDOUT prima di eseguire l'elenco composto e lo ripristina in seguito. Pertanto exec >nell'elenco composto non ha effetto oltre il punto in cui il vecchio descrittore viene ripristinato come STDOUT.

Diamo un'occhiata alla parte rilevante di 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

Puoi vedere come, alla riga 134, il descrittore 1( STDOUT) viene copiato su almeno un altro descrittore con indice 10(ecco cosa F_DUPFDfa; restituisce il descrittore più basso disponibile a partire da un determinato numero dopo aver duplicato su quel descrittore). Vedi anche come, alla riga 137, il risultato di open("/dev/null")(descrittore 3) viene copiato sul descrittore 1( STDOUT). Infine, on line 147, il vecchio STDOUTdescrittore salvato sul descrittore 10viene copiato nuovamente sul descrittore 1( STDOUT). L'effetto netto è quello di isolare la modifica STDOUTin linea 144(che corrisponde a quella interna exec >/dev/null).


Poiché FD 1 viene sovrascritto da FD 3 sulla riga 137, perché la riga 141 non punta 10 su / dev / null?
Joey Pabalinas,

@JoeyPabalinas La linea 141 sta duplicando FD 1 (cioè stdout) al successivo descrittore disponibile dopo 10 , che risulta essere 11, come puoi vedere nel valore restituito da quella chiamata di sistema. 10 è semplicemente codificato in bash in modo che il salvataggio del descrittore di bash non interferisca con i descrittori a una cifra che potresti manipolare nel tuo script exec.
Depresso

Quindi fcntl (1, F_DUPFD, 10) farà sempre riferimento a STDOUT, indipendentemente da dove FD 1 stia puntando?
Joey Pabalinas,

@JoeyPabalinas Non sono sicuro di quale sia la tua domanda. FD 1 È STDOUT. Sono la stessa cosa.
Depresso

Aggiunto output completo per il mio post originale.
Joey Pabalinas,

8

La differenza tra { exec >/dev/null; } >/dev/null; echo "Hi"e { exec >/dev/null; }; echo "Hi"è che il doppio reindirizzamento fa dup2(10, 1);prima di chiudere fd 10 che è la copia dell'originale stdout, prima di eseguire il comando successivo ( echo).

Succede in questo modo perché il reindirizzamento esterno sta effettivamente sovrapponendo il reindirizzamento interno. Ecco perché copia il file originale stdoutuna volta completato.


+1 per spiegare la differenza in modo semplice. La risposta di AlexP manca di questa spiegazione.
Kamil Maciorowski il
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.