Lazy Diary @ Hatena Blog

PowerShell / Java / miscellaneous things about software development, Tips & Gochas. CC BY-SA 4.0/Apache License 2.0

grep(1)にPassThru相当のオプションはない ほか

  • sh(1)のパイプの中で左辺のコマンドの戻り値を${PIPESTATUS[0]}で参照することはできない。
  • grep(1)でマッチの結果にかかわらず、すべての行を標準出力へ出力することはできない。
  • tee(1)でパイプの入力を複数のファイルディスクリプタに複製して出力することはできない。

何をやろうとしたかというと、ウイルススキャンの結果を取得したかっただけです。

ユーザから渡されたファイルを、処理の前にウイルススキャンする(スキャン陽性だったらユーザにメッセージを返す)処理があったりします。ウイルススキャンソフトにプログラミング言語用のAPIがあればいいんですが、ない場合はコマンドラインのスキャナを呼び出すことになります。こんな感じ。

/path/to/scanner /path/to/tmp/tmpXXXXXX

ここでtmpXXXXXXはウイルススキャン毎に作られる一時ディレクトリと思ってください。

標準出力をsyslogに吐きたい

scannerは見つかったマルウェアの名前を標準出力に吐きます。なので、その内容はログに取っておきたい。syslogを使うとして、こんな感じにします。

/path/to/scanner /path/to/tmp/tmpXXXXXX | logger -t scanner

陽性・陰性を戻り値で取得したい

ただ、scannerは戻り値でスキャンの陽性・陰性を返します。上記のままだとscannerコマンドでなくてパイプライン全体の戻り値が返ってしまうので、PIPESTATUS変数を使ってscannerコマンドの戻り値だけを参照します。

/path/to/scanner /path/to/tmp/tmpXXXXXX | logger -t scanner; exit ${PIPESTATUS[0]}

標準出力の内容を引っ掛けたい

さて、ここでひとつ困ったことがあって、このscannerコマンドは指定されたディレクトリにアクセスできないと「マルウェアの含まれるファイルが1つもなかった=陰性」と判断する仕様になっています。tmpXXXXXXディレクトリのパーミッションは文書管理システムが内部で一時ディレクトリを作るときに指定しているので、アクセスできないというのはシステムにバグがある。なので、この状況を引っ掛けてエラーなり何なり返したい。

scannerは標準出力にスキャンしたファイル数を出力します。指定されたディレクトリにアクセスできなかった場合は「files: 0」という文字列が標準出力に出るので、これを引っかけましょう。

/path/to/scanner /path/to/tmp/tmpXXXXXX | grep -F 'files: 0' | logger -t scanner; exit ${PIPESTATUS[0]}

困ったこと

さて、すると困ったことに、grep -F 'files: 0' の結果はどこにも返せません。それに、syslogには標準出力に'files: 0'があるときしかログが残らなくなっちゃいます。

まずは「scannerコマンドの標準出力に'files: 0'という内容があった場合は専用の戻り値を返したい。ただしそのような内容がなかった場合はscannerコマンドの戻り値をそのまま返したい」という状況を解決したいわけです。ただ、パイプラインの中で、パイプラインの最初に実行しているscannerコマンドの戻り値は参照できません。${PIPESTATUS[0]}を参照すると、パイプラインの左辺ではなく、前回実行したパイプラインの戻り値を参照していまいます。*1 こんな感じ。

$ echo -n
$ sh -c "exit 1" | echo ${PIPESTATUS[0]}
0
$ sh -c "exit 2" | echo ${PIPESTATUS[0]}
1
$ sh -c "exit 3" | echo ${PIPESTATUS[0]}
2

それから「grep(1)の結果がどうあれ、grep(1)に入ってきた内容をそのままlogger(1)コマンドに渡したい」という状況も解決したい。PowerShellだと-PassThruオプションを持たせたcmdletの実装事例がありましたが、grep(1)には同様のオプションはないようです。

パイプの入力をtee(1)で複数のファイルディスクリプタに複製して出力したりできないかな?というのも調べましたが、そんなオプションはないみたい。

*1:パイプの右辺のプログラムの実行中は左辺のプログラムもまだ動いているから当たり前といえば当たり前ですね