シェルにおいて、バックグラウンド実行のプロセスIDは$!
で取得することができる。
sleep 10 &
echo $! # sleepコマンドのプロセスIDが取得できる。
では、コマンドがパイプで繋がれている場合はどうなるだろうか?
tail -f log.txt | grep 'keyword' &
echo $!
この場合、$!
で取得されるのは、パイプの後半のプロセスgrep
コマンドのプロセスになる。
ここで問題となるのはkill $!
として、grep
をキルしてもtail
のプロセスが残ってしまうことである。
$ kill $! # grepのみがキルされる
$ ps aux | grep tail # tailのプロセスがまだ残っている
# > 9210 0.0 0.0 34122900 900 s000 SN 6:17PM 0:00.00 tail -f log.txt
前半のプロセスのプロセスIDを取得するにはどうすれば良いだろうか?
答えを言うと以下のようにすればよい。
tail -f log.txt > >(grep 'keyword') &
echo $! # $!はtailのプロセスIDになっている
ここで見慣れない文法 >( コマンド )
というものがある。
これはコマンド置換と呼ばれるbashの文法で、コマンドを実行したときのIOをファイルのように扱うことができるものである。
(参考:https://techblog.raccoon.ne.jp/archives/53726690.html )
つまり、tail -f ...
の結果をリダイレクトした先は本来ならファイルであるはずだが、そのファイルへの書き出しがgrep ...
の標準入力に渡されるようになる。
一般にプロセス置換を使うと cmd1 | cmd2
とパイプで繋ぐものと等価なものがcmd1 > >(cmd2)
として実現できる。
しかし、パイプと違うのがバックグラウンド実行した際に$!
で得られるプロセスID。コマンド置換を使った場合、前半のtail
のコマンドのプロセスIDが取得される。
これによってkill $!
としても、tailが残ってしまうことなく終了するし、ついでにgrepプロセスも終了する。