Uso pratico per spostare i descrittori di file


16

Secondo la pagina man di bash:

L'operatore di reindirizzamento

   [n]<&digit-

sposta il descrittore digitdi file sul descrittore di file no sull'input standard (descrittore di file 0) se nnon specificato. digitviene chiuso dopo essere stato duplicato n.

Che cosa significa "spostare" un descrittore di file in un altro? Quali sono le situazioni tipiche di tale pratica?

Risposte:


14

3>&4-è un'estensione ksh93 supportata anche da bash e che è l'abbreviazione di 3>&4 4>&-, che ora è 3 indica dove 4 era usato e 4 ora è chiuso, quindi ciò che è stato indicato da 4 è ora spostato su 3.

L'uso tipico sarebbe nei casi in cui hai duplicato stdino stdoutper salvarne una copia e vuoi ripristinarlo, come in:

Supponiamo di voler catturare lo stderr di un comando (e solo stderr) lasciando stdout da solo in una variabile.

Sostituzione comando var=$(cmd), crea una pipe. L'estremità di scrittura della pipe diventa lo cmdstdout (descrittore di file 1) e l'altra estremità viene letta dalla shell per riempire la variabile.

Ora, se si vuole stderrandare alla variabile, si potrebbe fare: var=$(cmd 2>&1). Ora sia fd 1 (stdout) che 2 (stderr) vanno al pipe (e alla fine alla variabile), che è solo la metà di ciò che vogliamo.

Se lo facciamo var=$(cmd 2>&1-)(abbreviazione di var=$(cmd 2>&1 >&-), ora solo lo cmdstderr va al pipe, ma fd 1 è chiuso. Se cmdtenta di scrivere qualsiasi output, ciò restituirebbe un EBADFerrore, se apre un file, otterrà il primo fd gratuito e il file aperto verrà assegnato a stdoutmeno che il comando non lo protegga! Neanche quello che vogliamo.

Se vogliamo che lo stdout di cmdsia lasciato solo, cioè puntare alla stessa risorsa a cui puntava al di fuori della sostituzione del comando, allora dobbiamo in qualche modo portare quella risorsa all'interno della sostituzione del comando. Per questo possiamo fare una copia stdout dell'esterno della sostituzione del comando per portarlo dentro.

{
  var=$(cmd)
} 3>&1

Quale è un modo più pulito di scrivere:

exec 3>&1
var=$(cmd)
exec 3>&-

(che ha anche il vantaggio di ripristinare fd 3 invece di chiuderlo alla fine).

Quindi su {(o su exec 3>&1) e fino a }, sia fd 1 che 3 indicano la stessa risorsa che fd 1 indicava inizialmente. fd 3 punterà anche a quella risorsa all'interno della sostituzione del comando (la sostituzione del comando reindirizza solo fd 1, stdout). Quindi sopra, per cmd, abbiamo per fds 1, 2, 3:

  1. il tubo a var
  2. intatto
  3. come quello che indica 1 al di fuori della sostituzione del comando

Se lo cambiamo in:

{
  var=$(cmd 2>&1 >&3)
} 3>&1-

Quindi diventa:

  1. come quello che indica 1 al di fuori della sostituzione del comando
  2. il tubo a var
  3. come quello che indica 1 al di fuori della sostituzione del comando

Ora, abbiamo quello che volevamo: stderr va alla pipa e stdout rimane intatto. Tuttavia, stiamo perdendo quel fd 3 a cmd.

Mentre i comandi (per convenzione) assumono che i fds da 0 a 2 siano aperti e siano input, output ed errori standard, non assumono nulla degli altri fds. Molto probabilmente lasceranno intatto quel fd 3. Se hanno bisogno di un altro descrittore di file, faranno semplicemente un open()/dup()/socket()...che restituirà il primo descrittore di file disponibile. Se (come uno script shell che lo fa exec 3>&1) hanno bisogno di usarlo in modo fdspecifico, lo assegneranno prima a qualcosa (e in quel processo, la risorsa detenuta dal nostro fd 3 verrà rilasciata da quel processo).

È buona pratica chiudere quel fd 3 poiché cmdnon lo usa, ma non è un grosso problema se lo lasciamo assegnato prima di chiamare cmd. I problemi possono essere: che cmd(e potenzialmente altri processi che genera) ha un fd in meno disponibile. Un problema potenzialmente più grave è se la risorsa a cui punta fd potrebbe finire trattenuta da un processo generato da quello cmdin background. Può essere un problema se quella risorsa è una pipe o un altro canale di comunicazione tra processi (come quando lo script viene eseguito comescript_output=$(your-script) ), poiché ciò significa che il processo di lettura dall'altra estremità non vedrà mai la fine del file fino a quando il processo in background termina.

Quindi qui, è meglio scrivere:

{
  var=$(cmd 2>&1 >&3 3>&-)
} 3>&1

Che, con bashpuò essere abbreviato in:

{
  var=$(cmd 2>&1 >&3-)
} 3>&1

Per riassumere i motivi per cui viene usato raramente:

  1. è zucchero non standard e solo sintattico. Devi bilanciare il salvataggio di alcune sequenze di tasti rendendo il tuo script meno portatile e meno ovvio per le persone che non sono abituate a quella caratteristica insolita.
  2. La necessità di chiudere il file fd originale dopo averlo duplicato viene spesso trascurato perché la maggior parte delle volte non ne subiamo le conseguenze, quindi facciamo semplicemente >&3invece di >&3-o >&3 3>&-.

Prova che viene usato raramente, come hai scoperto è che è falso in bash . In bash compound-command 3>&4-o any-builtin 3>&4-foglie fd 4 chiuso anche dopo compound-commando any-builtinè tornato. Una patch per risolvere il problema è ora disponibile (2013-02-19).


Grazie, ora so cosa significa spostare un fd. Ho 4 domande di base relative al 2 ° frammento (e fds in generale): 1) In cmd1, fai 2 (stderr) per essere una copia di 3, e se il comando usasse internamente questo 3 fd? 2) Perché 3> & 1 e 4> & 1 funzionano? La duplicazione di 3 e 4 ha effetto solo in quei due cmds, anche la shell corrente è interessata? 3) Perché chiudi 4 in cmd1 e 3 in cmd2? Quei comandi non usano i cd menzionati, vero? 4) Nell'ultimo frammento, cosa succede se un fd è duplicato in uno inesistente (in cmd1 e cmd2), intendo rispettivamente 3 e 4?
Quentin,

@Quentin, ho usato un esempio più semplice e ho spiegato un po 'di più sperando che ora sollevi meno domande di quante non risponda. Se hai ancora domande non direttamente correlate alla sintassi mobile fd, ti suggerisco di porre una domanda separata
Stéphane Chazelas,

{ var=$(cmd 2>&1 >&3) ; } 3>&1-Non è un refuso nella chiusura di 1?
Quentin,

@Quentin, è un errore di battitura in cui non penso di aver intenzione di includerlo, tuttavia non fa alcuna differenza dal momento che nulla all'interno delle parentesi graffe utilizza quel fd 1 (era l'intero punto di duplicarlo in fd 3: perché l'originale 1 altrimenti non sarebbe accessibile all'interno $(...)).
Stéphane Chazelas,

1
@Quentin Entrando {...}, fd 3 indica ciò che fd 1 utilizzato per indicare e fd 1 viene chiuso, quindi entrando $(...), fd 1 viene impostato sul tubo che si alimenta $var, quindi anche per cmd2 su quello, e quindi 1 su quali 3 punti a, quello è l'esterno 1. Il fatto che 1 rimanga chiuso in seguito è un bug in bash, lo segnalerò. ksh93 da cui proviene quella funzione non ha quel bug.
Stéphane Chazelas,

4

Significa farlo puntare allo stesso posto dell'altro descrittore di file. Hai bisogno di fare questo molto raramente, a parte l'ovvia gestione separata del descrittore di errore standard ( stderr, fd 2, /dev/stderr -> /proc/self/fd/2). Può tornare utile in alcuni casi complessi.

La guida Advanced Bash Scripting ha questo esempio di livello di registro più lungo e questo frammento:

# Redirecting only stderr to a pipe.
exec 3>&1                              # Save current "value" of stdout.
ls -l 2>&1 >&3 3>&- | grep bad 3>&-    # Close fd 3 for 'grep' (but not 'ls').
#              ^^^^   ^^^^
exec 3>&-                              # Now close it for the remainder of the script.

Nella Stregoneria del Mago della Fonte, ad esempio, lo usiamo per discernere output diversi dallo stesso blocco di codice:

  (
    # everything is set, so run the actual build infrastructure
    run_build
  ) 3> >(tee -a $C_LOG >> /dev/stdout) \
    2> >(tee -a $C_LOG 1>&2 > $VOYEUR_STDERR) \
     > >(tee -a $C_LOG > $VOYEUR_STDOUT)

È stata aggiunta una sostituzione di processo aggiuntiva per motivi di registrazione (VOYEUR decide se i dati devono essere visualizzati sullo schermo o semplicemente registrati), ma alcuni messaggi devono essere sempre presentati. Per farlo, li stampiamo sul descrittore di file 3 e quindi lo gestiamo in modo speciale.


0

In Unix, i file sono gestiti da descrittori di file (numeri interi piccoli, ad esempio l'input standard è 0, l'output standard è 1, l'errore standard è 2; quando si aprono altri file normalmente vengono assegnati al descrittore inutilizzato più piccolo). Quindi, se conosci gli inards del programma e vuoi inviare l'output che va al descrittore di file 5 all'output standard, dovresti spostare il descrittore 5 su 1. Ecco da dove 2> errorsproviene, e costruzioni come 2>&1duplicare gli errori in il flusso di output.

Quindi, quasi mai usato (ricordo vagamente di averlo usato una o due volte con rabbia nei miei oltre 25 anni di utilizzo quasi esclusivo di Unix), ma quando è assolutamente necessario.


Ma perché non duplicare il descrittore di file 1 nel modo seguente: 5> & 1? Non capisco a che serve spostare FD poiché il kernel sta per chiuderlo subito dopo ...
Quentin,

Ciò non duplica 5 in 1, invia 5 dove 1 sta andando. E prima di chiedere; sì, ci sono diverse notazioni diverse, raccolte da una varietà di gusci precursori.
vonbrand,

Ancora non capisco. Se 5>&1invia 5 a dove sta andando 1, cosa fa esattamente 1>&5-, oltre a chiudere 5?
Quentin,
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.