Quiero enviar el demonio a un segundo plano y solo continuar con la ejecución de mi script cuando el demonio envíe la línea particular al stderr, algo entre las líneas:
# Fictional daemon
{
for x in {1..9}; do
sleep 1
if [ "$x" != "5" ]; then
echo $x 1>&2
else
echo now 1>&2
fi
done
} &
# Daemon PID
PID="$!"
# Wait until the particular output...
until { read -r line && grep -q "now" <<<"$line"; } do
sleep 1
done </proc/$PID/fd/2
#
# Do more stuff...
#
fg
Respuesta1
Con un mydaemon
que se comporta como:
#! /bin/sh -
i=1 stage=init
while true; do
if [ "$i" -eq 5 ]; then
echo >&2 ready
stage=working
fi
echo >&2 "$stage $i"
sleep 1
i=$((i + 1))
done
Es decir, que tarda 4 segundos en inicializarse, sale ready
cuando está listo y continúa trabajando desde entonces, todo en el mismo proceso, podría escribir un start-mydaemon
script como:
#! /bin/sh -
DAEMON=./mydaemon
LOGFILE=mydaemon.log
umask 027
: >> "$LOGFILE" # ensures it exists
pid=$(
sh -c 'echo "$$"; exec "$0" "$@"' tail -fn0 -- "$LOGFILE" | {
IFS= read -r tail_pid
export LOGFILE DAEMON
setsid -f sh -c '
echo "$$"
exec "$DAEMON" < /dev/null >> "$LOGFILE" 2>&1'
grep -q ready
kill -s PIPE "$tail_pid"
}
)
printf '%s\n' "$DAEMON started in process $pid and now ready"
$ time ./start-mydaemon
./mydaemon started in process 230254 and now ready
./start-mydaemon 0.01s user 0.01s system 0% cpu 4.029 total
$ ps -fjC mydaemon
UID PID PPID PGID SID C STIME TTY TIME CMD
chazelas 230254 6175 230254 230254 0 10:28 ? 00:00:00 /bin/sh - ./mydaemon
start-mydaemon
no regresa hasta que mydaemon
haya indicado que estaba listo. Aquí, mydaemon
stdout y stderr van a un archivo de registro, mientras que su stdin se redirige desde /dev/null
. setsid -f
(no es un comando estándar, pero se encuentra en la mayoría de las distribuciones de Linux) debería garantizar que el demonio esté desconectado de la terminal (si se inicia desde una).
Sin embargo, tenga en cuenta que si mydaemon
no se inicializa y muere sin siquiera escribir ready
, ese script esperará para siempre hasta que ready
nunca llegará (o llegará la próxima vez que mydaemon
se inicie correctamente).
También tenga en cuenta que sh -c ...tail
y el demonio se inician simultáneamente. Si mydaemon
ya se ha inicializado e impreso ready
cuando tail
ha comenzado el tiempo y ha buscado hasta el final del archivo de registro, tail
se perderá el ready
mensaje.
Podrías abordar esos problemas con algo como:
#! /bin/sh -
DAEMON=./mydaemon
LOGFILE=mydaemon.log
export DAEMON LOGFILE
umask 027
died=false ready=false canary_pid= tail_pid= daemon_pid=
: >> "$LOGFILE" # ensures it exists
{
exec 3>&1
{
tail -c1 <&5 5<&- > /dev/null # skip to the end synchronously
(
sh -c 'echo "tail_pid=$$" >&3; exec tail -fn+1' |
{ grep -q ready && echo ready=true; }
) <&5 5<&- &
} 5< "$LOGFILE"
setsid -f sh -c '
echo "daemon_pid=$$" >&3
exec "$DAEMON" < /dev/null 3>&- 4>&1 >> "$LOGFILE" 2>&1' 4>&1 |
(read anything; echo died=true) &
echo "canary_pid=$!"
} | {
while
IFS= read -r line &&
eval "$line" &&
! "$died" &&
! { [ -n "$daemon_pid" ] && "$ready" ; }
do
continue
done
if "$ready"; then
printf '%s\n' "$DAEMON started in process $daemon_pid and now ready"
else
printf >&2 '%s\n' "$DAEMON failed to start"
fi
kill -s PIPE "$tail_pid" "$canary_pid" 2> /dev/null
"$ready"
}
Aunque eso está empezando a ser bastante complicado. También tenga en cuenta que, dado que tail -f
ahora funciona en stdin, en Linux, no utilizará inotify para detectar cuándo hay nuevos datos disponibles en el archivo y recurrirá a lo habitual.comprobar cada segundolo que significa que puede tardar hasta un segundo más en detectarse ready
en el archivo de registro.
Respuesta2
... done </proc/$PID/fd/2
Eso no funciona como crees.
El estándar de $PID
es
- el tty controlador, en cuyo caso intentará leer una cadena ingresada por el usuario en lo que probablemente también sea elentrada estándarde
$PID
- una pipa: competirías con quien ya esté leyendo en ella, lo que resultaría en un completo desastre
/dev/null
--¡EOF!- algo más ;-) ?
Solo hay formas ingeniosas de redirigir a otro lugar un descriptor de archivo desde un proceso en ejecución, por lo que lo mejor que puede hacer es hacer que su código de espera de entrada se degrade y se ejecute cat >/dev/null
en segundo plano.
Por ejemplo, esto "esperará" hasta que el demonio genere 4
:
% cat /tmp/daemon
#! /bin/sh
while sleep 1; do echo $((i=i+1)) >&2; done
% (/tmp/daemon 2>&1 &) | (sed /4/q; cat >/dev/null &)
1
2
3
4
%
Después de lo cual /tmp/daemon
continuará escribiendo en cat >/dev/null &
, fuera del control de Shell.
Otra solución sería redirigir el stderr del demonio a algún archivo normal y tail -f
en él, pero luego el demonio seguirá llenando su disco con basura (incluso si guarda rm
el archivo, el espacio que ocupa no se liberará hasta que el demonio lo cierra), lo cual es incluso peor que tener a una persona de bajos recursos cat
holgazaneando.
Por supuesto, lo mejor será escribir /tmp/daemon
como un demonio real, que se pone en segundo plano después de la inicialización, cierra sus descriptores de archivos estándar, los utiliza syslog(3)
para errores de impresión, etc.
Respuesta3
Otras respuestas implican el uso de archivos o canalizaciones con nombre. No necesitas tampoco. Una simple pipa es suficiente.
#!/bin/bash
run_daemon() {
# Fictional daemon
{
for x in {1..9}; do
sleep 1
if [ "$x" != "5" ]; then
echo $x 1>&2
else
echo now 1>&2
fi
done
}
}
run_daemon 2>&1 | (
exec 9<&0 0</dev/null
while read -u 9 line && test "$line" != now
do
echo "$line" ## or ## :
done
echo "$line" ## or ##
cat /dev/fd/9 & ## or ## cat /dev/fd/9 >/dev/null &
#fictional stuff
for a in 1 2 3
do
echo do_stuff $a
sleep 1
done
echo done
wait
)
Las secciones marcadas ## or ##
son alternativas si no desea mostrar la salida del demonio mezclada. Tenga en cuenta que no puede omitir el gato porque es lo único que impide que el demonio obtenga un SIGPIPE cuando sale.
Intenté no redirigir la entrada a un descriptor de archivo diferente (el exec
), pero sin eso, algo cierra la entrada y el demonio finaliza.
También puedes notar que hice que el demonio no estuviera explícitamente en segundo plano. Con la tubería no es necesario. Espera wait
al gato, no al demonio. Todavía funciona si el demonio está en segundo plano.
wait
es análogo a fg
, pero no hace que la consola controle. (También es mucho más antiguo).
Respuesta4
mkfifo
puede hacer lo que necesita. Vea esta respuesta:
https://stackoverflow.com/questions/43949166/reading-value-of-stdout-and-stderr
Utilice un FIFO en su lugar
Técnicamente, /dev/stdout y /dev/stderr son en realidad descriptores de archivos, no FIFO ni canalizaciones con nombre. En mi sistema, en realidad son solo enlaces simbólicos a /dev/fd/1 y /dev/fd/2. Esos descriptores suelen estar vinculados a su TTY o PTY. Por lo tanto, realmente no puedes leerlos de la manera que estás intentando hacerlo.
Este código hace lo que estás buscando (eliminé los pasos de procesamiento). No es necesario dormir en el bucle que lee. Tenga en cuenta que cuando la lectura de código del quince se detiene, el demonio continuará intentando escribir en él hasta que se llene y el demonio se bloqueará hasta que algo se lea del quince.
fifo="daemon_errors"
{
mkfifo $fifo
for x in {1..9}; do
sleep 1
if [ "$x" != "5" ]; then
echo $x 1>&2
else
echo now 1>&2
fi
done 2>$fifo
} &
sleep 1 # need to wait until the file is created
# Read all output
while read -r line; do
echo "just read: $line"
done < $fifo