SSHClient.exec_command
を使って一つずつコマンドを実行することはできるのですが、既存のシェルスクリプトを流しこみたいときどうしたらいいんだろうと試行錯誤しました。
TLDR
明示的にバッファをフラッシュするためにシェルスクリプトの終わりにexit\n
を付ける。
作業ログ
import getpass
import paramiko
REMOTE_HOST = "YOUR_REMOTE_HOST"
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
pkey = paramiko.RSAKey.from_private_key_file('/home/ubuntu/.ssh/id_rsa', getpass.getpass())
c.connect(REMOTE_HOST, username='ubuntu', pkey=pkey)
# こういう複数行のやつを流しこみたい
cmd = '''
/bin/ls /
/bin/touch /tmp/foo
echo 'foo
bar
baz' >> /tmp/foo
cat /tmp/foo
'''
まずbashを起動して標準入力で流しこむ方法を試しました。
stdin, stdout, stderr = c.exec_command('bash -')
stdin.write(cmd)
print(stdout.read())
しかし応答せず。
どこかでブロックしているのかと考え次にselectの方法を試しました。
import socket
import select
chan = c.invoke_shell()
chan.setblocking(False)
chan.send(cmd)
while True:
r, _, _ = select.select([chan], [], [])
if chan in r:
try:
x = chan.recv(1024)
if len(x) == 0:
break
print(x)
except socket.timeout:
pass
これでも応答せず。ブロックしているというわけではなさそうなことが分かりました。
じゃあ何だろう、バッファかなと。
ローカルの送信側のバッファは明示的にフラッシュすればいいのですが、リモートホストのバッファをフラッシュする方法が分からず。調べてみたらシェルスクリプトを終了させれば自動的にフラッシュすると記述があったので、スクリプトを修正してみました。
cmd = '''
/bin/ls /
/bin/touch /tmp/foo
echo 'foo
bar
baz' >> /tmp/foo
cat /tmp/foo
exit
'''
動いた。なるほど。
まとめ
- 同期非同期の問題ではなかった。
- やりたいことが終わったらプロセスを落とす。シェルかソケットのバッファだろうがプロセス落とすときにはフラッシュされる。