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 stdin
o stdout
per 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 cmd
stdout (descrittore di file 1) e l'altra estremità viene letta dalla shell per riempire la variabile.
Ora, se si vuole stderr
andare 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 cmd
stderr va al pipe, ma fd 1 è chiuso. Se cmd
tenta di scrivere qualsiasi output, ciò restituirebbe un EBADF
errore, se apre un file, otterrà il primo fd gratuito e il file aperto verrà assegnato a stdout
meno che il comando non lo protegga! Neanche quello che vogliamo.
Se vogliamo che lo stdout di cmd
sia 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:
- il tubo a var
- intatto
- 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:
- come quello che indica 1 al di fuori della sostituzione del comando
- il tubo a var
- 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 fd
specifico, 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é cmd
non 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 cmd
in 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 bash
può essere abbreviato in:
{
var=$(cmd 2>&1 >&3-)
} 3>&1
Per riassumere i motivi per cui viene usato raramente:
- è 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.
- 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
>&3
invece 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-command
o any-builtin
è tornato. Una patch per risolvere il problema è ora disponibile (2013-02-19).