All Articles

シェル以外からexpectしててハマったメモ

また恥ずかしながらハマったメモ記事です><

シェル以外からexpectすると上手くいかなかった

シェル以外、と書いてますが、今回はPHPのexec()でのケースです。おそらくcronでもなんでもシェル以外からの起動だと同じだと思います。

やりたかったこと

PHPからシェルスクリプトを起動して、別のサーバに自動でscp転送をかける、ということがしたかったんです。 で、別サーバは認証鍵が必要なので、自動でパスフレーズを入力するためにexpectを使おうかなという感じで。

※あとで知って残念な気持ちになったんですが、expectでの自動化よりも、パスフレーズ無しの認証鍵で、authrized_keysによるコマンド制限などを行う方がよりよい方法だと知りました…(´・ω・`)

以下のサイトが参考に。

ssh scp sftp の正しい自動実行方法 sshで指定したコマンドしか実行できない公開鍵を作る

上記の方法がベターのようです。

ハマったポイント

上記の方法を知らずにexpectでやってやんよ!と書いたのが以下のスクリプト。この手のはそこら中にサンプルがあるみたいですが、勉強のため書いてみました。

#!/bin/sh

HOST=xxx.xxx.xxx.xxx
PASS=xxxxxxxxxxx
IDENT=/home/user/.ssh/id_rsa

expect -c "
set timeout 10
spawn scp -i $IDENT /path/to/local user@$HOST:/path/to/remote
expect \"Enter passphrase for key\"
send \"${PASS}\r\"
interact
"

で、これをautoscp.shなど名前をつけて、実行権限をつけて、exec('/path/to/autoscp.sh');などとしてPHPから実行したんですが、どうにも上手く転送されず・・・。試しにシェルから実行するとちゃんと転送するんです。

解決方法

方法というか見れば分かりますが、問題だったのはexpect内の"interact"の記述。これを付けるもんだと思い込んでたのがダメでした。恥ずかしい…。interactのコマンド結果は、

interactはspawnで作成されたジョブの標準入出力を、キーボードと画面にする。
すなわち通常の通信になる。
<p style="text-align:right">引用元:<a href="http://blog.majide.com/2006/01/how-to-use-expect/" target="_blank">Expect の使い方</a></p>

という事で、標準入出力をシェルに戻す感じなんですね。マニュアルよく読め。で、プログラム内ではシェルに戻す事ができず、そのまま止まってしまうようでした。今回はscpなので、転送結果の出力行が100%の文字を含んだらexitするようにもう一つexpectを書くことで解決しました。修正後のコードは以下です:

#!/bin/sh

HOST=xxx.xxx.xxx.xxx
PASS=xxxxxxxxxxx
IDENT=/home/user/.ssh/id_rsa

expect -c "
set timeout 10
spawn scp -i $IDENT /path/to/local user@$HOST:/path/to/remote
expect \"Enter passphrase for key\"
send \"${PASS}\r\"
expect {\"100%\" { exit 0 }} # 100%の文字列が表示されたらexitする
"

こうすることで、exec()からの結果も転送完了表示が出て問題なく転送できました! cronでやる場合にも気をつけないといけないですね・・・('A`)

最近こういう細かいことで躓くことが多くなってきて勉強不足を痛感しますね・・・