Para beneficio del lector, esta receta aquí
- se puede volver a usar como línea para atrapar stderr en una variable
- todavía da acceso al código de retorno del comando
- Sacrifica un descriptor de archivo temporal 3 (que usted puede cambiar, por supuesto)
- Y no expone estos descriptores de archivos temporales al comando interno
Si desea captura stderrde algunos commanden varque puede hacer
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
Después lo tienes todo:
echo "command gives $? and stderr '$var'";
Si commandes simple (no algo así a | b), puede dejar el interior {}alejado:
{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;
Envuelto en una función fácil de reutilizar bash(probablemente necesita la versión 3 y superior para local -n):
: catch-stderr var cmd [args..]
catch-stderr() { local -n v="$1"; shift && { v="$("$@" 2>&1 1>&3 3>&-)"; } 3>&1; }
Explicado:
local -n alias "$ 1" (que es la variable para catch-stderr )
3>&1 usa el descriptor de archivo 3 para guardar sus puntos estándar
{ command; } (o "$ @") luego ejecuta el comando dentro de la captura de salida $(..)
- Tenga en cuenta que el orden exacto es importante aquí (hacerlo de forma incorrecta baraja los descriptores de archivo incorrectamente):
2>&1redirige stderra la captura de salida$(..)
1>&3redirige stdoutlejos de la captura de salida de $(..)nuevo al "exterior" stdoutque se guardó en el descriptor de archivo 3. Tenga en cuenta questderr todavía se refiere a donde FD 1 señaló antes: a la captura de salida$(..)
3>&-luego cierra el descriptor de archivo 3 ya que ya no es necesario, de modo que commandno aparece repentinamente un descriptor de archivo abierto desconocido. Tenga en cuenta que la carcasa externa todavía tiene FD 3 abierto, perocommand no lo verá.
- Esto último es importante, porque algunos programas como
lvmquejarse de descriptores de archivos inesperados. Y se lvmqueja stderr, ¡justo lo que vamos a capturar!
Puede capturar cualquier otro descriptor de archivo con esta receta, si se adapta en consecuencia. Excepto el descriptor de archivo 1, por supuesto (aquí la lógica de redireccionamiento sería incorrecta, pero para el descriptor de archivo 1 puede usarvar=$(command) como de costumbre).
Tenga en cuenta que esto sacrifica el descriptor de archivo 3. Si necesita ese descriptor de archivo, no dude en cambiar el número. Pero tenga en cuenta que algunos proyectiles (de la década de 1980) podrían entenderse 99>&1como un argumento 9seguido de 9>&1(esto no es un problema para bash).
También tenga en cuenta que no es particularmente fácil hacer que este FD 3 sea configurable a través de una variable. Esto hace que las cosas sean muy ilegibles:
: 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 de seguridad: los primeros 3 argumentos catch-var-from-fd-by-fdno deben tomarse de un tercero. Dales siempre explícitamente de forma "estática".
Entonces no-no-no catch-var-from-fd-by-fd $var $fda $fdb $command , , ¡nunca hagas esto!
Si pasa un nombre de variable variable, al menos hágalo de la siguiente manera:
local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command
Esto aún no lo protegerá contra cada exploit, pero al menos ayuda a detectar y evitar errores comunes de secuencias de comandos.
Notas:
catch-var-from-fd-by-fd var 2 3 cmd.. es lo mismo que catch-stderr var cmd..
shift || returnes solo una forma de evitar errores feos en caso de que olvide dar el número correcto de argumentos. Quizás terminar el shell sería otra forma (pero esto dificulta la prueba desde la línea de comandos).
- La rutina fue escrita de tal manera que es más fácil de entender. Uno puede reescribir la función de manera que no la necesite
exec, pero luego se vuelve realmente fea.
- Esta rutina puede ser reescrita para no
bashtan bien que no hay necesidad de hacerlo local -n. Sin embargo, entonces no puedes usar variables locales y ¡se vuelve extremadamente feo!
- También tenga en cuenta que los
evals se utilizan de manera segura. Por evallo general, se considera peligroso. Sin embargo, en este caso no es más malvado que usar "$@"(para ejecutar comandos arbitrarios). Sin embargo, asegúrese de utilizar la cita exacta y correcta como se muestra aquí (de lo contrario, se vuelve muy muy peligroso ).
ERROR=$(./useless.sh | sed 's/Output/Useless/' 2>&1 1>/dev/ttyX)