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'");
何をやってるかというと、バッファリングを無くしたgrep
でtail -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 とか使ってたらもっとかっこよく書けるけどないものねだりしない。