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 stderrdi alcuni commandin varsi 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 -nalias "$ 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>&1reindirizza stderralla cattura dell'output$(..)
1>&3reindirizza stdoutlontano dall'output catturando $(..)indietro su "esterno" stdoutche è stato salvato nel descrittore di file 3. Nota che stderrfa 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 commandall'improvviso non compaiano alcuni descrittori di file aperti sconosciuti. Si noti che la shell esterna ha ancora FD 3 aperto, ma commandnon lo vedrà.
- Quest'ultimo è importante, perché alcuni programmi come
lvmlamentano descrittori di file imprevisti. E si lvmlamenta 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>&1come argomento 9seguito 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-fdnon 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
bashcosì bene che non è necessario local -n. Tuttavia, non è possibile utilizzare le variabili locali e diventa estremamente brutto!
- Si noti inoltre che le
evals 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)