cronで前に起動しているプロセスを殺したい時の罠


pkill

一般的なサービスではあまりない状況かもしれないですが、趣味で常時起動しているプログラムなどを定期的に再実行させたりしたい事はよくあると思います。
そんな時に特定のプロセスをkillして、再度開始用のコマンドを実行したいのですがプロセスのIDは毎回変わるのでIDからkillするのはやりにくいです。そんな時に役に立つのが pkill です。

pkill -f python

とやるとpythonで実行されているプログラムを一掃します。基本的な認識としては

ps aux | grep python

これで見つかるプロセスを一掃してくれるイメージです。

cronでpkill

これでいい感じにkillはできるのでその後実行したいと思いcronにこう書いたとします。

0 7 * * * pkill -f python && python /path/to/example.py

毎日7時にexample.pyを実行しているプロセスを再起動させようとしています。
しかし実はこれだとプロセスのkillはされますがexample.pyはpythonで実行されません。

※ pythonを例に出していますが、どんなコマンドに関しても同様です。

この現象が起きた時現象をググりたくてもなんて検索したら良いのか分からず、cron関連の問題だと思ってだいぶ時間を使ってしまいました…
(pkillのexitコードが && に続かないものなのかと思ったりして && を ; に変えてみたり…)

ただ、背景がわかると至極当然の流れでした。

まず前提として

ps aux | grep python

このコマンドを実行した時に、 ps aux | grep python このコマンドそのものも結果として出力されています。pythonという文字を含むプロセスを検索しているのですから、検索しているプロセスそのものもヒットするのは当たり前すぎて見逃しがちですがここにヒントが有りました。

pkillの-fオプションで指定した文字列もgrepと同様自分自身のプロセスも検索対象として検索します。その結果、 pkill -f python && python /path/to/example.py を実行しているプロセスはkill対象となります。なので自分で自分をkillします。まさかの自殺です 😊

あとはわかると思いますがpkill実行後にpythonコマンドを実行するプロセスがpkill実行時に死んでいくのですからpythonコマンドは実行されません。

対策

原因は分かったとしてじゃあどうしたら良いのか。

実はpkillの-fオプションには正規表現が使えます。なので

0 7 * * * pkill -f pytho[n] && /path/to/startscript.sh

こんな風に書きます。

前半は正規表現でpythoと[n]([]はその中の任意の文字)なので python はこの正規表現にヒットします。しかし pytho[n] という文字列は pytho[n] という正規表現ではヒットしません。何を言っているか分からなくなりそうですが前者は文字列として、後者は正規表現として見るというのがミソです。

さらに後半に python /path/to/example.py と書いてしまうとここが pytho[n] にヒットしてしまうのでスクリプトにしてしまって python という文字を含まないように実行します。
このスクリプト内でpythonを実行すればプロセスとしてはpythonという文字列を含んだプロセスができ、そのプロセスを殺すことで実質終了させられる、かつcronで実行されたコマンド自体はkillされずに再度/path/to/startscript.shの実行まで進める、という訳ですね。

おんなじ悩みを抱える人がどこかにいる気がしたので書いてみました。