デーモンをバックグラウンドに送り、デーモンが次の行のいずれかを 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
Linuxではstdinで動作しているため、ファイル内に新しいデータが利用可能になったときにinotifyを使用せず、通常の方法に頼ることに注意してください。毎秒チェックするつまり、ready
ログ ファイルで検出するのに最大 1 秒余分にかかる可能性があります。
答え2
... done </proc/$PID/fd/2
それはあなたが思っているようには機能しません。
のstderr$PID
は
- 制御端末の場合、ユーザーが入力した文字列を、おそらく制御端末の標準入力の
$PID
- パイプの場合、すでにパイプから読み込んでいる人と競合することになり、大混乱に陥ることになります。
/dev/null
-- EOF!- 何か他のもの ;-) ?
実行中のプロセスからファイル記述子を他の場所にリダイレクトする方法はハッキーな方法しかないため、入力待機コードをダウングレードしてバック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
うろつかせるよりもさらに悪い状況です。
もちろん、最善の方法は、初期化後にバックグラウンドで実行し、std ファイル記述子を閉じ、エラーの印刷などに/tmp/daemon
使用する実際のデーモンとして記述することです。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 ##
デーモンの出力を混在させて表示したくない場合の代替手段です。デーモンが出力時に SIGPIPE を取得するのを防ぐ唯一のものは cat であるため、cat を省略できないことに注意してください。
入力を別のファイル記述子 ( exec
) にリダイレクトしないようにしてみましたが、そうしないと何かが入力を閉じ、デーモンが終了します。
また、デーモンを明示的にバックグラウンドにしないようにしたことにも注意してください。パイプがあれば、これは必要ありません。wait
デーモンではなく、cat を待機します。デーモンがバックグラウンドになっている場合でも、動作します。
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 から何かを読み取るまでデーモンはブロックされることに注意してください。
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