Ich möchte den Daemon in den Hintergrund schicken und die Ausführung meines Skripts erst fortsetzen, wenn der Daemon die bestimmte Zeile an stderr ausgibt, etwas zwischen den Zeilen:
# 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
Antwort1
Mit einem mydaemon
, das sich wie folgt verhält:
#! /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
Das heißt, die Initialisierung dauert 4 Sekunden, es gibt eine Ausgabe aus, ready
wenn es bereit ist, und von da an arbeitet alles im selben Prozess weiter. Sie könnten ein start-mydaemon
Skript wie das folgende schreiben:
#! /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
kehrt erst zurück, wenn mydaemon
angezeigt wurde, dass es bereit ist. Dabei mydaemon
gehen stdout und stderr in eine Protokolldatei, während stdin von umgeleitet wird /dev/null
. setsid -f
(kein Standardbefehl, aber in den meisten Linux-Distributionen vorhanden) sollte garantieren, dass der Daemon vom Terminal getrennt ist (falls er von einem gestartet wurde).
Beachten Sie jedoch, dass, wenn mydaemon
die Initialisierung fehlschlägt und das Skript beendet wird, ohne jemals geschrieben zu haben ready
, das Skript ewig auf ein wartet, ready
das nie kommt (oder beim nächsten mydaemon
erfolgreichen Start kommt).
Beachten Sie auch, dass sh -c ...tail
und der Daemon gleichzeitig gestartet werden. Wenn zum Zeitpunkt des Starts mydaemon
bereits initialisiert und gedruckt wurde und bis zum Ende der Protokolldatei gesucht wurde, wird die Meldung übersehen .ready
tail
tail
ready
Sie könnten diese Probleme etwa folgendermaßen angehen:
#! /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"
}
Allerdings wird das langsam ziemlich kompliziert. Beachten Sie auch, dass tail -f
es jetzt auf stdin läuft, unter Linux wird es inotify nicht verwenden, um zu erkennen, wann neue Daten in der Datei verfügbar sind, und auf das übliche zurückgreifenjede Sekunde prüfenDies bedeutet, dass es bis zu einer zusätzlichen Sekunde dauern kann, bis es ready
in der Protokolldatei erkannt wird.
Antwort2
... done </proc/$PID/fd/2
Das funktioniert nicht so, wie Sie denken.
Der Standardfehler von $PID
ist entweder
- das steuernde TTY, in diesem Fall werden Sie versuchen, eine vom Benutzer eingegebene Zeichenfolge zu lesen, was wahrscheinlich auch derstdinvon
$PID
- eine Pipe - Sie würden mit demjenigen konkurrieren, der bereits daraus liest, was zu einem kompletten Durcheinander führen würde
/dev/null
-- Schluss mit dem Teufel!- etwas anderes ;-) ?
Es gibt nur Hacks, um einen Dateideskriptor aus einem laufenden Prozess an einen anderen Ort umzuleiten. Daher ist es am besten, wenn Sie Ihren auf die Eingabe wartenden Code so herunterstufen, dass er cat >/dev/null
im Hintergrund ausgeführt wird.
Dies „wartet“ beispielsweise, bis der Daemon Folgendes ausgibt 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
%
Danach /tmp/daemon
wird mit dem Schreiben fortgefahren cat >/dev/null &
, außerhalb der Kontrolle der Shell.
Eine andere Lösung besteht darin, den Standardfehler des Daemons auf eine normale Datei umzuleiten und tail -f
darauf zu klicken. Dadurch füllt der Daemon Ihre Festplatte jedoch weiterhin mit Datenmüll (selbst wenn Sie rm
die Datei haben, wird der von ihr belegte Speicherplatz erst freigegeben, wenn der Daemon sie schließt). Dies ist noch schlimmer, als wenn eine Datei mit geringen Ressourcen cat
herumliegt.
Am besten ist es natürlich, es /tmp/daemon
als echten Daemon zu schreiben, der sich nach der Initialisierung selbst in den Hintergrund stellt, seine Standarddateideskriptoren schließt, syslog(3)
zum Drucken von Fehlern verwendet usw.
Antwort3
Andere Antworten beinhalten die Verwendung von Dateien oder benannten Pipes. Sie brauchen beides nicht. Eine einfache Pipe reicht aus.
#!/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
)
Die markierten Abschnitte ## or ##
sind Alternativen, wenn Sie die Ausgabe des Daemons nicht eingemischt anzeigen möchten. Beachten Sie, dass Sie die Katze nicht weglassen können, da sie das einzige ist, was verhindert, dass der Daemon bei der Ausgabe ein SIGPIPE erhält.
Ich habe versucht, die Eingabe nicht auf einen anderen Dateideskriptor (den exec
) umzuleiten, aber ohne das schließt etwas die Eingabe und der Daemon wird beendet.
Möglicherweise fällt Ihnen auch auf, dass ich den Daemon nicht explizit in den Hintergrund gestellt habe. Mit der Pipe ist das nicht nötig. Er wait
wartet auf die Katze, nicht auf den Daemon. Er funktioniert trotzdem, wenn der Daemon im Hintergrund ist.
wait
ist analog zu fg
, steuert die Konsole aber nicht. (Es ist auch viel älter.)
Antwort4
mkfifo
kann tun, was Sie brauchen. Siehe diese Antwort:
https://stackoverflow.com/questions/43949166/reading-value-of-stdout-and-stderr
Verwenden Sie stattdessen ein FIFO
Technisch gesehen sind /dev/stdout und /dev/stderr eigentlich Dateideskriptoren, keine FIFOs oder benannten Pipes. Auf meinem System sind sie eigentlich nur symbolische Links zu /dev/fd/1 und /dev/fd/2. Diese Deskriptoren sind normalerweise mit Ihrem TTY oder PTY verknüpft. Sie können sie also nicht so lesen, wie Sie es versuchen.
Dieser Code macht das, was Sie suchen (ich habe die Verarbeitungsschritte entfernt). In der Schleife, die liest, ist kein Ruhezustand erforderlich. Beachten Sie, dass der Daemon, wenn der Code zum Lesen aus dem FIFO gestoppt wurde, weiterhin versucht, in den FIFO zu schreiben, bis er voll ist, und der Daemon blockiert, bis etwas aus dem FIFO gelesen wird.
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