Я хочу отправить демон в фоновый режим и продолжить выполнение скрипта только тогда, когда демон выведет определенную строку в stderr, что-то из строк:
# 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
решение1
С , mydaemon
который ведет себя как:
#! /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
То есть, инициализация занимает 4 секунды, вывод ready
по готовности и продолжение работы в том же процессе, вы можете написать start-mydaemon
такой скрипт:
#! /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
не возвращается, пока mydaemon
не укажет, что он готов. Здесь mydaemon
stdout и stderr направляются в файл журнала, а его stdin перенаправляется из /dev/null
. setsid -f
(нестандартная команда, но есть в большинстве дистрибутивов Linux) должна гарантировать, что демон отсоединен от терминала (если запущен с одного из них).
Однако следует отметить, что если mydaemon
инициализация не удалась и скрипт завершается, так и не записав ready
, то он будет вечно ждать , ready
который никогда не появится (или появится при следующем mydaemon
успешном запуске).
Также обратите внимание, что sh -c ...tail
и демон запускаются одновременно. Если к моменту запуска mydaemon
уже инициализировался и напечатал и дошел до конца файла журнала, сообщение будет пропущено .ready
tail
tail
ready
Вы можете решить эти проблемы с помощью чего-то вроде:
#! /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"
}
Хотя это начинает быть довольно запутанным. Также обратите внимание, что поскольку tail -f
теперь он работает на stdin, в Linux он не будет использовать inotify для обнаружения новых данных в файле и прибегнет к обычномупроверяйте каждую секундуready
Это означает, что для обнаружения в файле журнала может потребоваться до одной дополнительной секунды .
решение2
... done </proc/$PID/fd/2
Это не работает так, как вы думаете.
Stderr — это $PID
либо
- управляющий tty, в этом случае вы попытаетесь прочитать строку, введенную пользователем, в то время как это, вероятно, такжестандартный вводиз
$PID
- трубка — вы будете конкурировать с тем, кто уже читает из нее, что приведет к полной неразберихе
/dev/null
-- Конец света!- что-то другое ;-) ?
Существуют только хакерские способы перенаправить куда-либо дескриптор файла из запущенного процесса, поэтому лучшим вариантом будет перевести код ожидания ввода в режим cat >/dev/null
работы в фоновом режиме.
Например, это будет «ждать», пока демон не выведет 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
%
После чего /tmp/daemon
продолжит запись в cat >/dev/null &
, вне контроля оболочки.
Другим решением было бы перенаправить stderr демона в какой-нибудь обычный файл и tail -f
на него, но тогда демон продолжит заполнять ваш диск мусором (даже если вы удалите rm
файл, занимаемое им место не освободится, пока демон его не закроет), что даже хуже, чем иметь файл с низким потреблением ресурсов, который будет слоняться cat
без дела.
Конечно, лучше всего будет написать /tmp/daemon
настоящий демон, который уходит в фоновый режим после инициализации, закрывает свои дескрипторы файлов std, использует syslog(3)
для печати ошибки и т. д.
решение3
Другие ответы предполагают использование файлов или именованных каналов. Вам не нужно ни то, ни другое. Достаточно простого канала.
#!/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
)
Отмеченные разделы ## or ##
являются альтернативными вариантами, если вы не хотите показывать смешанный вывод демона. Обратите внимание, что вы не можете пропустить cat, поскольку это единственное, что мешает демону получать SIGPIPE при выводе.
Я пробовал не перенаправлять ввод в другой файловый дескриптор ( exec
), но без этого что-то закрывает ввод и демон завершает работу.
Вы также можете заметить, что я сделал демона явно не фоновым. С конвейером он не нужен. Ожидает wait
кота, а не демона. Он все равно работает, если демон фоновым.
wait
аналогичен fg
, но не делает консоль управляющей. (Она также намного старше.)
решение4
mkfifo
может сделать то, что вам нужно. Смотрите этот ответ:
https://stackoverflow.com/questions/43949166/чтение-значения-stdout-и-stderr
Вместо этого используйте FIFO
Технически, /dev/stdout и /dev/stderr на самом деле являются файловыми дескрипторами, а не FIFO или именованными каналами. В моей системе они на самом деле просто символические ссылки на /dev/fd/1 и /dev/fd/2. Эти дескрипторы обычно связаны с вашим TTY или PTY. Поэтому вы не можете читать из них так, как пытаетесь сделать.
Этот код делает то, что вы ищете (я удалил этапы обработки). В цикле чтения сон не нужен. Обратите внимание, что когда чтение кода из fifo остановлено, демон продолжит попытки записи в него, пока он не заполнится, и демон заблокируется, пока что-нибудь не прочитает из fifo.
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