我想將守護程序發送到後台,並且僅當守護程序將特定行輸出到 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
在日誌檔案中進行檢測。
答案2
... done </proc/$PID/fd/2
這並不像你想像的那樣有效。
的 stderr$PID
是
- 控制 tty,在這種情況下,您將嘗試讀取使用者輸入的字串,該字串也可能是標準輸入的
$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 &
,不受 shell 控制。
另一個解決方案是將守護程序的 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 ##
如果您不想顯示混合的守護程序的輸出,則標記的部分是替代方案。
我嘗試不將輸入重定向到不同的檔案描述符(exec
),但如果沒有這個,某些東西會關閉輸入並且守護程式終止。
您可能還注意到,我沒有明確地將守護程式設定為後台。有了管道就不需要了。等待wait
的是貓,而不是守護程式。如果守護程式處於後台,它仍然有效。
wait
類似於fg
,但不使控制台進行控制。 (它也老得多。)
答案4
mkfifo
可以做你需要的。看這個答案:
https://stackoverflow.com/questions/43949166/reading-value-of-stdout-and-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