具体的には以下のようなコマンドを実行するとき、
ps | grep bash | grep -v grep
その行全体のExit code(次のecho $?で表示されるもの)はpsコマンドの出力にbashが含まれるか含まれないかで決まる。bashが含まれれば行全体のコマンド実行結果、$?は0になるし、検索文字列をbashじゃなくcashみたいな存在しないプロセス名に置き換えれば行全体として失敗し、$?は1になる。
つまり、例えば以下のようなrsyslogdの起動状態を確認するシェルスクリプトでgrep -v grepと差し込んでも、rsyslogdプロセスの存在を確認するコマンドとして有効である。
ps aux | grep /usr/sbin/rsyslogd | grep -v grep > /dev/null
if [ $? == 0 ]; then echo true; else echo false; fi
上記を、grep -v grep部分のExit codeが分からないなとか考えて、代わりにwc -lの行数で判別しようと以下の様にするのは、悪い。
l=$(ps aux | grep /usr/sbin/rsyslogd | wc -l)
if [ $l -gt 2 ]; then echo true; else echo false; fi
何が悪いかというと、ps auxの実行結果には「grep ~」のプロセスがごくまれに含まれない場合が有って、たまに、rsyslogdプロセスは存在するにも関わらず「wc -l」の実行結果としては「1」が出力されてしまい、プログラムがrsyslogが存在しないと判断を誤るからである。結構判りづらいバグになる。
対して1番目の例は、ps auxの出力にgrepが含まれようが含まれまいが、「/usr/sbin/rsyslogd」が含まれていれば行全体としてのExit codeは0で、rsyslogdがなければ1である。
というか、最近2番目を使うシェルスクリプトを書いてへまをした反省をメモにしたためているのが本記事である。
なぜそうなるのか
もう少し掘り下げると、grep -vのExit codeは、最終的な出力行数が0行になるときに1になり、-vを掛けても何らかの出力行がある場合は0になる。
つまり、aaaというプロセスをチェックしたいとして、psコマンドの出力には以下のようにaaaが含まれ、
aaa
bbb
となる時、
ps | grep aaa
の出力は、
aaa
で、これにgrep -v grepを掛けても、その出力は
aaa
となり、1行出力があるので最終的なgrep -v grepのExit codeは0になる。
逆に、psコマンドの出力にaaaが含まれない以下、
bbb
ccc
となる時、
ps | grep aaa
の出力は1行も無く
となり、そこにgrep -v grepを掛けても、その出力は空のままで、
この時、出力行は0行となるのでgrep -v grepのExit codeは1になる。
また、psコマンドの出力にaaaが含まれず、grep aaaが含まれる以下、
bbb
ccc
grep aaa
の場合は、
ps | grep aaa
の出力結果は以下のようになり、
grep aaa
そこにgrep -v grepを掛けると、その出力は最終的に空となり、
結果として出力行は0行となるので、この時のgrep -v grepのExit codeは1になる。
いやー、上手くできているものだと感心しきり。
なお、その他のテクとしてgrep検索文字列の1文字目を[]で囲む方法もあるが、検索文字列が変数となる場合、例えばスクリプト内で以下のように書くときは都合が悪い。
p=/usr/sbin/rsyslog
ps aux | grep "$p" | grep -v grep > /dev/null
if [ $? ~
さらに、シェルスクリプト内でプロセスの存在を確認するならpgrepを使うのが良いかもしれない。pgrep -fで、プロセスのコマンド行全体を検索することもできるようだ。