Per il beneficio del lettore, questa ricetta qui
- può essere riutilizzato come oneliner per catturare stderr in una variabile
- dà ancora accesso al codice di ritorno del comando
- Sacrifica un descrittore di file temporaneo 3 (che può essere modificato da te ovviamente)
- E non espone questi descrittori di file temporanei al comando interno
Se si desidera catturare stderr
di alcuni command
in var
si può fare
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
Dopo hai tutto:
echo "command gives $? and stderr '$var'";
Se command
è semplice (non qualcosa del genere a | b
) puoi lasciare l'interno {}
lontano:
{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;
Avvolto in una semplice funzione riutilizzabile bash
(probabilmente necessita della versione 3 e successive per local -n
):
: catch-stderr var cmd [args..]
catch-stderr() { local -n v="$1"; shift && { v="$("$@" 2>&1 1>&3 3>&-)"; } 3>&1; }
Ha spiegato:
local -n
alias "$ 1" (che è la variabile per catch-stderr
)
3>&1
usa il descrittore di file 3 per salvare lì punti stdout
{ command; }
(o "$ @") quindi esegue il comando all'interno della cattura dell'output $(..)
- Si prega di notare che l'ordine esatto è importante qui (farlo nel modo sbagliato mescola erroneamente i descrittori di file):
2>&1
reindirizza stderr
alla cattura dell'output$(..)
1>&3
reindirizza stdout
lontano dall'output catturando $(..)
indietro su "esterno" stdout
che è stato salvato nel descrittore di file 3. Nota che stderr
fa ancora riferimento a dove FD 1 puntava prima: All'acquisizione output$(..)
3>&-
quindi chiude il descrittore di file 3 poiché non è più necessario, in modo tale che command
all'improvviso non compaiano alcuni descrittori di file aperti sconosciuti. Si noti che la shell esterna ha ancora FD 3 aperto, ma command
non lo vedrà.
- Quest'ultimo è importante, perché alcuni programmi come
lvm
lamentano descrittori di file imprevisti. E si lvm
lamenta di stderr
: proprio quello che cattureremo!
Puoi prendere qualsiasi altro descrittore di file con questa ricetta, se ti adatti di conseguenza. Tranne il descrittore di file 1 ovviamente (qui la logica di reindirizzamento sarebbe errata, ma per il descrittore di file 1 puoi semplicemente usare var=$(command)
come al solito).
Nota che questo sacrifica il descrittore di file 3. Se ti capita di aver bisogno di quel descrittore di file, sentiti libero di cambiare il numero. Ma attenzione, alcune shell (dagli anni '80) potrebbero comprendere 99>&1
come argomento 9
seguito da 9>&1
(questo non è un problema bash
).
Si noti inoltre che non è particolarmente facile rendere questo FD 3 configurabile tramite una variabile. Questo rende le cose molto illeggibili:
: catch-var-from-fd-by-fd variable fd-to-catch fd-to-sacrifice command [args..]
catch-var-from-fd-by-fd()
{
local -n v="$1";
local fd1="$2" fd2="$3";
shift 3 || return;
eval exec "$fd2>&1";
v="$(eval '"$@"' "$fd1>&1" "1>&$fd2" "$fd2>&-")";
eval exec "$fd2>&-";
}
Nota di sicurezza: i primi 3 argomenti catch-var-from-fd-by-fd
non devono essere presi da una terza parte. Dagli sempre esplicitamente in modo "statico".
Quindi no-no-no catch-var-from-fd-by-fd $var $fda $fdb $command
, non farlo mai!
Se ti capita di passare un nome di variabile variabile, almeno fallo come segue:
local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command
Questo comunque non ti proteggerà da ogni exploit, ma almeno aiuterà a rilevare ed evitare errori di scripting comuni.
Appunti:
catch-var-from-fd-by-fd var 2 3 cmd..
equivale a catch-stderr var cmd..
shift || return
è solo un modo per prevenire brutti errori nel caso in cui si dimentichi di fornire il numero corretto di argomenti. Forse terminare la shell sarebbe un altro modo (ma questo rende difficile testare dalla riga di comando).
- La routine è stata scritta in modo tale che è più facile da capire. Si può riscrivere la funzione in modo che non sia necessaria
exec
, ma diventa davvero brutta.
- Questa routine può essere riscritta per non
bash
così bene che non è necessario local -n
. Tuttavia, non è possibile utilizzare le variabili locali e diventa estremamente brutto!
- Si noti inoltre che le
eval
s sono utilizzate in modo sicuro. Di solito eval
è considerato pericoloso. Tuttavia, in questo caso non è più malvagio che usare "$@"
(per eseguire comandi arbitrari). Tuttavia, assicurati di utilizzare il preventivo esatto e corretto come mostrato qui (altrimenti diventa molto molto pericoloso ).
ERROR=$(./useless.sh | sed 's/Output/Useless/' 2>&1 1>/dev/ttyX)