Pour le bénéfice du lecteur, cette recette ici
- peut être réutilisé comme oneliner pour capturer stderr dans une variable
- donne toujours accès au code retour de la commande
- Sacrifie un descripteur de fichier temporaire 3 (qui peut être modifié par vous bien sûr)
- Et n'expose pas ces descripteurs de fichiers temporaires à la commande interne
Si vous voulez attraper stderr
de certains command
en var
vous pouvez faire
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
Ensuite, vous avez tout:
echo "command gives $? and stderr '$var'";
Si command
est simple (pas quelque chose comme a | b
) vous pouvez laisser l'intérieur {}
loin:
{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;
Enveloppé dans une fonction réutilisable facile bash
(nécessite probablement la version 3 et supérieure pour local -n
):
: catch-stderr var cmd [args..]
catch-stderr() { local -n v="$1"; shift && { v="$("$@" 2>&1 1>&3 3>&-)"; } 3>&1; }
Expliqué:
local -n
alias "$ 1" (qui est la variable pour catch-stderr
)
3>&1
utilise le descripteur de fichier 3 pour y enregistrer les points stdout
{ command; }
(ou "$ @") exécute alors la commande dans la capture de sortie $(..)
- Veuillez noter que l'ordre exact est important ici (le faire dans le mauvais sens mélange mal les descripteurs de fichiers):
2>&1
redirige stderr
vers la capture de sortie$(..)
1>&3
redirige stdout
loin de la capture de sortie $(..)
vers le "externe" stdout
qui a été enregistré dans le descripteur de fichier 3. Notez que stderr
fait toujours référence à l'endroit où FD 1 pointait auparavant: à la capture de sortie$(..)
3>&-
puis ferme le descripteur de fichier 3 car il n'est plus nécessaire, de sorte que command
tout à coup un descripteur de fichier ouvert inconnu n'apparaisse pas. Notez que la coque externe a toujours FD 3 ouvert, mais command
ne le verra pas.
- Ce dernier est important, car certains programmes comme se
lvm
plaignent de descripteurs de fichiers inattendus. Et se lvm
plaint de stderr
- exactement ce que nous allons capturer!
Vous pouvez attraper n'importe quel autre descripteur de fichier avec cette recette, si vous vous adaptez en conséquence. Sauf le descripteur de fichier 1 bien sûr (ici la logique de redirection serait fausse, mais pour le descripteur de fichier 1, vous pouvez simplement l'utiliser var=$(command)
comme d'habitude).
Notez que cela sacrifie le descripteur de fichier 3. Si vous avez besoin de ce descripteur de fichier, n'hésitez pas à changer le numéro. Mais sachez que certains shells (des années 1980) pourraient être interprétés 99>&1
comme un argument 9
suivi de 9>&1
(ce n'est pas un problème pour bash
).
Notez également qu'il n'est pas particulièrement simple de rendre ce FD 3 configurable via une variable. Cela rend les choses très illisibles:
: 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>&-";
}
Note de sécurité: les 3 premiers arguments de catch-var-from-fd-by-fd
ne doivent pas provenir d'un tiers. Donnez-les toujours explicitement de façon «statique».
Alors non catch-var-from-fd-by-fd $var $fda $fdb $command
, non , ne fais jamais ça!
Si vous passez un nom de variable, faites-le au moins comme suit:
local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command
Cela ne vous protégera toujours pas contre tous les exploits, mais aidera au moins à détecter et à éviter les erreurs de script courantes.
Remarques:
catch-var-from-fd-by-fd var 2 3 cmd..
est le même que catch-stderr var cmd..
shift || return
est juste un moyen d'éviter de vilaines erreurs au cas où vous oublieriez de donner le nombre correct d'arguments. Peut-être que terminer le shell serait une autre façon (mais cela rend le test difficile à partir de la ligne de commande).
- La routine a été écrite de telle sorte qu'elle est plus facile à comprendre. On peut réécrire la fonction de telle sorte qu'elle n'en ait pas besoin
exec
, mais alors ça devient vraiment moche.
- Cette routine peut également être réécrite pour non-
bash
ainsi que ce n'est pas nécessaire local -n
. Cependant, vous ne pouvez pas utiliser de variables locales et cela devient extrêmement moche!
- Notez également que les
eval
s sont utilisés de manière sûre. eval
Est généralement considéré comme dangereux. Cependant, dans ce cas, ce n'est pas plus mal que d'utiliser "$@"
(pour exécuter des commandes arbitraires). Cependant, assurez-vous d'utiliser la citation exacte et correcte comme indiqué ici (sinon cela devient très très dangereux ).
ERROR=$(./useless.sh | sed 's/Output/Useless/' 2>&1 1>/dev/ttyX)