Perlでssh tail -fして目的の行が来たら終了する

簡単だろうと思って始めてみたら意外とハマった。。。例えばネットワーク機器の設定確認とかで、ロードバランサ経由でアクセスしてみてちゃんとアクセスが来てるか確認したい時に、人力だったらターミナル開いてアクセスログをtail -fしといてcurlとかで叩いて「きたきた」ってやって終わりでいいと思うんですが、台数がべらぼうに多いときとかムリポ。

そこで、サーバにsshしつつtail -fを発行してアクセスログをフェッチしつつ、grep的なことをして目的のログが来たら終了、みたいな処理を考えてみた。方針はたぶんこの 2 種類。

  • sshで渡すコマンドでgrepして目的の行が出たらsshが終了するようにしておく
  • 目的の行は perl で探して、見つけたらsshのプロセスにシグナルを送る

なんでこんなめんどくさいことが必要かというと、tail -fは明示的に殺すまで基本的にはずっと動き続けるので、なんらかの方法で終わらせなきゃいけないから。

bash でtail -fを終了させる方法

この場合、sshに渡す shell script が重要になる。1 行見つかったら終了させるなら例えばこんな感じ。

my $cmd = "PID=\$\$; tail -F -n0 /tmp/hoge.log | grep --line-buffered hoge | while read l; do kill \$PID; done";
system("ssh -t $host '$cmd'");

何をやってるかというと、バッファリングを無くしたgreptail -fをフィルタして、その後のwhileループに 1 行目が入るとsshで実行されてる shell 自体に自殺してもらう。あとはこの続きで後処理を書けば良い。

まぁどうみてもめんどいというか、これ perl じゃない。

perl でpipe openで立ち上げたプロセスを終了させる

色々調べてたら実はシンプルだった。パイプ指定した場合openの返り値がforkしたプロセスのPIDになっているので、そこにシグナルを送ればいい。

ちなみにどちらもssh -tしているのはこちらのシグナルを届かせるためで、つけないとリモート側にtail -fがずっと残っちゃったりする。んで、後者の方でKilled by signal 15.ってのが標準エラー出力に出てしまうので消そうと思って、openに渡すのを配列じゃなくて shell にしてみたら、tty がぶっ壊れたのでやめた。何を言ってるか分からない人は試しに上のスクリプトでopenのところを下の様な感じにしてみるといい(環境によってはおかしくならないのかも)。

my $pid = open my $fh, '-|', "$ssh $host '$cmd' 2>/dev/null" or die;

まぁやっつけスクリプトだったのでこれ以上は深入りしなかった。何か知ってる事のある人いればツッコミお待ちしています。

おわりに

ちなみに今思うと、サーバにスクリプト送り込んじゃったら簡単だったかなぁとかちょっと思ったけど、まぁいい勉強になったということでご愛嬌。

しかし、シグナル周りやっぱりよく分かってないし、さらにssh絡むとマジで難しいので、こういうのはあんまり自分でスクラッチしない方がいい気がした。かぴなんとかとか、とまぬまんとかとか、たぶん似たようなことできるんじゃないかなーと勝手に思ってる。

あと fluentd とか zookeeper とか使ってたらもっとかっこよく書けるけどないものねだりしない。