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とか使ってたらもっとかっこよく書けるけどないものねだりしない。