script - Bash: la salida de captura del comando se ejecuta en segundo plano
ejecutar un proceso en segundo plano (3)
Bash tiene una función llamada Sustitución de procesos para lograr esto.
$ echo <(yes)
/dev/fd/63
Aquí, la expresión <(yes)
se reemplaza con un nombre de ruta de un archivo (pseudodispositivo) que está conectado a la salida estándar de un trabajo asíncrono yes
(que imprime la cadena y
en un bucle infinito).
Ahora intentemos leer de él:
$ cat /dev/fd/63
cat: /dev/fd/63: No such file or directory
El problema aquí es que el proceso yes
terminó mientras tanto porque recibió un SIGPIPE (no tenía lectores en stdout).
La solución es la siguiente construcción
$ exec 3< <(yes) # Save stdout of the ''yes'' job as (input) fd 3.
Esto abre el archivo como entrada fd 3 antes de que se inicie el trabajo en segundo plano.
Ahora puede leer desde el trabajo de fondo cuando lo prefiera. Para un ejemplo estúpido
$ for i in 1 2 3; do read <&3 line; echo "$line"; done
y
y
y
Tenga en cuenta que esto tiene una semántica ligeramente diferente que hacer que el trabajo en segundo plano escriba en un archivo respaldado por la unidad: el trabajo en segundo plano se bloqueará cuando el búfer esté lleno (vaciar el búfer leyendo fd). Por el contrario, escribir en un archivo respaldado por una unidad solo está bloqueando cuando el disco duro no responde.
La sustitución de procesos no es una función POSIX sh.
Aquí hay un truco rápido para dar un respaldo de unidad de trabajo asíncrono (casi) sin asignarle un nombre de archivo:
$ yes > backingfile & # Start job in background writing to a new file. Do also look at `mktemp(3)` and the `sh` option `set -o noclobber`
$ exec 3< backingfile # open the file for reading in the current shell, as fd 3
$ rm backingfile # remove the file. It will disappear from the filesystem, but there is still a reader and a writer attached to it which both can use it.
$ for i in 1 2 3; do read <&3 line; echo "$line"; done
y
y
y
Recientemente, Linux también agregó la opción O_TEMPFILE, lo que hace posibles dichos ataques sin que el archivo sea visible en absoluto. No sé si bash ya lo admite.
ACTUALIZAR :
@rthur, si quiere capturar toda la salida de fd 3, use
output=$(cat <&3)
Pero tenga en cuenta que no puede capturar datos binarios en general: solo es una operación definida si el resultado es texto en el sentido POSIX. Las implementaciones que conozco simplemente filtran todos los bytes NUL. Además, POSIX especifica que todas las nuevas líneas finales deben ser eliminadas.
(Tenga en cuenta también que la captura de la salida dará como resultado OOM si el escritor nunca se detiene ( yes
nunca se detiene). Pero, naturalmente, ese problema se mantiene incluso para read
si el separador de línea nunca se escribe de forma adicional)
Intento escribir un script bash que obtenga el resultado de un comando que se ejecuta en segundo plano. Desafortunadamente no puedo hacer que funcione, la variable a la que asigné la salida está vacía; si reemplazo la asignación con un comando echo todo funciona como esperaba.
#!/bin/bash
function test {
echo "$1"
}
echo $(test "echo") &
wait
a=$(test "assignment") &
wait
echo $a
echo done
Este código produce el resultado:
echo
done
Cambiar la asignación a
a=`echo $(test "assignment") &`
funciona, pero parece que debería haber una mejor manera de hacerlo.
Una forma de capturar el resultado del comando de fondo es redirigir su salida en un archivo y capturar el resultado del archivo una vez que el proceso de fondo ha finalizado:
test "assignment" > /tmp/_out &
wait
a=$(</tmp/_out)
Una manera muy robusta de tratar con coprocesos en Bash es usar ... el coproc
incorporado.
Supongamos que tiene un script o una función llamada banana
que desea ejecutar en segundo plano, captura todo su resultado mientras hace algunas stuff
y espera hasta que finalice. Haré la simulación con esto:
banana() {
for i in {1..4}; do
echo "gorilla eats banana $i"
sleep 1
done
echo "gorilla says thank you for the delicious bananas"
}
stuff() {
echo "I''m doing this stuff"
sleep 1
echo "I''m doing that stuff"
sleep 1
echo "I''m done doing my stuff."
}
A continuación, ejecutará banana
con el coproc
la coproc
manera:
coproc bananafd { banana; }
esto es como ejecutar banana &
pero con los siguientes extras: crea dos descriptores de archivos que están en la matriz bananafd
(en el índice 0
para la salida y el índice 1
para la entrada). Capturará la salida de banana
con la read
integrada de read
:
IFS= read -r -d '''' -u "${bananafd[0]}" banana_output
Intentalo:
#!/bin/bash
banana() {
for i in {1..4}; do
echo "gorilla eats banana $i"
sleep 1
done
echo "gorilla says thank you for the delicious bananas"
}
stuff() {
echo "I''m doing this stuff"
sleep 1
echo "I''m doing that stuff"
sleep 1
echo "I''m done doing my stuff."
}
coproc bananafd { banana; }
stuff
IFS= read -r -d '''' -u "${bananafd[0]}" banana_output
echo "$banana_output"
Advertencia: ¡debes terminar con las stuff
antes de que el banana
termine! si el gorila es más rápido que tú:
#!/bin/bash
banana() {
for i in {1..4}; do
echo "gorilla eats banana $i"
done
echo "gorilla says thank you for the delicious bananas"
}
stuff() {
echo "I''m doing this stuff"
sleep 1
echo "I''m doing that stuff"
sleep 1
echo "I''m done doing my stuff."
}
coproc bananafd { banana; }
stuff
IFS= read -r -d '''' -u "${bananafd[0]}" banana_output
echo "$banana_output"
En este caso, obtendrá un error como este:
./banana: line 22: read: : invalid file descriptor specification
Puedes verificar si es demasiado tarde (es decir, si has tardado demasiado haciendo tus stuff
) porque después de que se coproc
el coproc
, bash elimina los valores en la matriz bananafd
, y es por eso que obtuvimos el error anterior.
#!/bin/bash
banana() {
for i in {1..4}; do
echo "gorilla eats banana $i"
done
echo "gorilla says thank you for the delicious bananas"
}
stuff() {
echo "I''m doing this stuff"
sleep 1
echo "I''m doing that stuff"
sleep 1
echo "I''m done doing my stuff."
}
coproc bananafd { banana; }
stuff
if [[ -n ${bananafd[@]} ]]; then
IFS= read -r -d '''' -u "${bananafd[0]}" banana_output
echo "$banana_output"
else
echo "oh no, I took too long doing my stuff..."
fi
Finalmente, si realmente no quieres perderte ninguno de los movimientos de gorila, incluso si tardas demasiado en tus stuff
, puedes copiar el descriptor de archivo de banana
a otro fd, 3
por ejemplo, haz tus cosas y luego lee de 3
:
#!/bin/bash
banana() {
for i in {1..4}; do
echo "gorilla eats banana $i"
sleep 1
done
echo "gorilla says thank you for the delicious bananas"
}
stuff() {
echo "I''m doing this stuff"
sleep 1
echo "I''m doing that stuff"
sleep 1
echo "I''m done doing my stuff."
}
coproc bananafd { banana; }
# Copy file descriptor banana[0] to 3
exec 3>&${bananafd[0]}
stuff
IFS= read -d '''' -u 3 output
echo "$output"
Esto funcionará muy bien! la última read
también jugará el rol de wait
, de modo que la output
contendrá la salida completa de banana
.
Eso fue genial: no hay archivos temporales para tratar (bash maneja todo en silencio) y 100% pure bash!
¡Espero que esto ayude!