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 stderr
de algunos command
en var
que puede hacer
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
Después lo tienes todo:
echo "command gives $? and stderr '$var'";
Si command
es 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>&1
redirige stderr
a la captura de salida$(..)
1>&3
redirige stdout
lejos de la captura de salida de $(..)
nuevo al "exterior" stdout
que 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 command
no 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
lvm
quejarse de descriptores de archivos inesperados. Y se lvm
queja 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>&1
como un argumento 9
seguido 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-fd
no 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 || return
es 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
bash
tan 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
eval
s se utilizan de manera segura. Por eval
lo 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)