やりたいこと
次のシェルスクリプトにおいて、 longjob の終了と同時に tail -f を終了させたい。
# !/bin/bash
longjob &>log & disown
tail -f log
条件:
- 1つのシェルスクリプト内で行いたい。
- 端末が終了しても
longjobは継続させたい (つまり、& disownは譲れない)。 -
Ctrl-cの押下時にtailは終了させたいがlongjobは終了させたくない。
解決策1
tail -f の開始前に、以下を行うバックグラウンドプロセスをあらかじめ起動しておく。
-
longjobが終了するまで待機。
-
tailのプロセスを kill。
# !/bin/bash
longjob &>log & disown
(
# set PID_JOB
PID_JOB=$!
# set PID_TAIL
while test -z $PID_TAIL; do PID_TAIL=$(ps --ppid=$$ | grep tail | awk '$0=$1'); done
# wait PID_JOB
while ps -p $PID_JOB >/dev/null; do sleep 0.5; done
# kill PID_TAIL
kill -INT $PID_TAIL
) &
tail -f log
-
tailの開始前にPID_TAIL=$(...)の部分が実行された場合、$PID_TAILは空文字になる。PID_TAILを確実に設定するために、# set PID_TAILの部分はwhileで回している。 - 問題点:
longjobが終了するとkill -INTが発砲され、tailが異常終了する。
解決策2
tail をバックグラウンドで一旦起動しおいて fg で処理を戻す。
# !/bin/bash -i
longjob &>log & disown
PID_JOB=$!
tail -f log &
PID_TAIL=$!
(
# wait PID_JOB
while ps -p $PID_JOB >/dev/null; do sleep 0.5; done
# kill PID_TAIL
kill -INT $PID_TAIL 2>/dev/null
) &
fg 1 >/dev/null
- 注意: シェバングが
#!/bin/bash -i。- fg はインタラクティブシェルにおいて利用可。
- インタラクティブシェルとしてシェルスクリプトを起動するため。
-
longjob終了前にCtrl-cした場合にkillが失敗するため、エラーを捨てる。 -
fgしたときにtail -f logと表示されるのを防ぐため、標準出力を捨てる。
うまく行かない方法
tee & trap
# Ctrl-c で longjob が終了してしまう
longjob 2>&1 | tee log
# Ctrl-c で終了できない
trap '' 1 2 15
nohup longjob 2>&1 </dev/null | tee log
# longjob は最後まで実行されるが、Ctrl-c で teeが終了し、それ以降のログが記録されない
# longjob に "trap '' 1 2 15" を記述した上で
nohup longjob 2>&1 </dev/null | tee log
longjob に trap '' 1 2 15 と tee log を記述した上で解決策2のようなコードを書けばできるが美しくはない。
wait
# wait PID_JOB の部分を wait $PID_JOB とするとこうなる。
wait: pid 5734 is not a child of this shell