シェルでtry-catch. 標準出力を渡しながら異常終了ステータスを検知する

  • 4
    いいね
  • 0
    コメント

これはVAddy Adventカレンダー12日目の記事です。
VAddyは、継続的なWeb脆弱性検査が簡単に実現できるSaaSです。

私が好きな国内クラフトビールに、志賀高原IPAがあります。
この会社は自分たちが飲みたいビールを作るをコンセプトに、ホップまで栽培しています。いいですねー!
いつか志賀高原でパウダースノーを堪能してホテルで最高の一杯を飲んでみたいものです。

シェルのパイプで繋いだ先で、パイプ元のコマンドの異常終了ステータスを検知する

前回の記事で、VAddyのWeb脆弱性検査を定期的に実行して結果をSlack通知を書きました。
この記事ではVAddyのCLIツールから、脆弱性検査開始し、結果取得して脆弱性があった場合のみSlackに通知する方法を載せました。
VAddyのコマンドの結果をパイプで繋いでSlack通知スクリプトに渡せば問題ないだろうと思っていたのですが、パイプだけではうまく動かなかったため今回はその思考錯誤の結果を書きます。

やりたかったこと

やりたかったことは、go-vaddyコマンドで脆弱性が発見された場合はコマンドが異常終了(ステータス1)するので、その時のみgo-vaddyコマンドが標準出力した結果をslackに投げることです。
わかりやすく言えば、
go-vaddy.exe | slack.sh
のような処理をしつつも、slack.shはgo-vaddy.exeが異常終了した時にのみslackに投稿する処理をしたいのです。
このため、クリアしなければいけない課題は下記になります。

  • go-vaddyコマンドの標準出力を、slackに投稿するコマンドslack.shで受け取る
  • slack.shは、go-vaddyコマンドが異常終了した時のみ動作する

1つ目はパイプだけで解決できますね。2つ目はリストを使って、go-vaddy.exe || slack.shのようにすれば解決できます。
ただ、それらを同時に満たすには簡単ではありませんでした。

結論

先に結論から書きますね。
今回は、シェルのtrapコマンドを使いました。
これにより、異常終了した時のみtrapが発動して指定した関数を実行できます。
try-catchみたいな動きですね。異常終了したコマンドがあれば、catch(trap)が実行され、slack投稿スクリプトが動作します。

#!/bin/sh

set -eu

trap catch ERR

function catch {
    echo "$MESSAGE" | ./slack.sh
}

MESSAGE=$(./go-vaddy/bin/vaddy-macosx64)

trap catch ERRで異常終了した時のみcatch()が実行されます。
go-vaddyの標準出力は、$MESSAGEに入るため、trapの処理でその変数をechoしてslack通知スクリプトに渡しています。

trapの最後のERRをEXITにすれば終了時に必ず実行するようになるため、finallyの動きができます。

試行錯誤

ここからは、trapにたどり着くまでに試した方法を書いていきます。

実験1: パイプだけでやる

go-vaddy.exe | slack.sh
最初に思いついたのは、パイプの後のコマンドslack.shの中で$?を使って最初に実行したコマンドの終了ステータスを取得して制御する方法です。
結果、パイプはコマンドの実行が同時に走るため前の処理の標準出力結果を待って処理することはできても、終了ステータスはうまく取得できませんでした。

実験2: ||リストを使って中間ファイルに出力する

go-vaddy.exe || slack.shのように||リストを使うと、go-vaddyが異常終了した時のみslack.shが実行できるようになります。ただ、go-vaddyの標準出力はパイプのようにslack.shに引き継ぎできません。
これを解決するために、中間ファイルを作って処理してみます。

go-vaddy.exe > hoge.txt || cat hoge.txt | slack.sh

いいですね。これでできました。
ただ中間ファイルが正常終了した時に削除することができないのが少しダサいですね。。やめておきましょう。

実験3: コマンド置換で標準出力を変数に入れて処理する

コマンド置換は、

FOO=`go-vaddy`
FOO=$(go-vaddy)

のように、コマンドの実行結果の出力を変数FOOに入れるものです。
これで出力を変数にいれて、コマンド置換の実行結果を$?で評価すれば良いかと思いました。

FOO=$(go-vaddy)
if [ $? -gt 0 ]; then
  cat "$FOO" | ./slack.sh
fi

これはgo-vaddyが正常終了した場合はうまく動くのですが、FOO=$(go-vaddy)が異常終了した時点でこのスクリプト全体も終了するため、ifまでたどり着かずうまくいきませんでした。

 さいごに

もしかしたらもっとエレガントな処理があるかもしれませんが、今回の仕様を満たすためにはtrapで十分そうだとわかりました。
今まで、パイプとリスト(&&, ||など)の動きを正確に理解して使っていなかったのだと反省しました。
シェルは大事ですね。これからシェル道に邁進していきたいと思います。