Pythonでシェルコマンドの実行結果をリストで渡す方法 において、こんなソースがあった。これ、考え方はいいんだけど、シェルコマンドの終了を待つという点が惜しい。
res_cmd_no_lfeed.py
#!/usr/bin/python
import subprocess
def res_cmd_lfeed(cmd):
return subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout.readlines()
def res_cmd_no_lfeed(cmd):
return [str(x).rstrip("\n") for x in res_cmd_lfeed(cmd)]
def main():
cmd = ("ls -l")
print(res_cmd_no_lfeed(cmd))
if __name__ == '__main__':
main()
そこで、yield を使う
res_cmd_no_lfeed.py
#!/usr/bin/python
import subprocess
def res_cmd_lfeed(cmd): # 戻り値はリストではなく各行
for line in subprocess.Popen(cmd, stdout=subprocess.PIPE,shell=True).stdout:
yield line
def res_cmd_no_lfeed(cmd):
return [str(x).rstrip("\n") for x in res_cmd_lfeed(cmd)]
def main():
cmd = ("ls -l")
print(res_cmd_no_lfeed(cmd))
if __name__ == '__main__':
main()
res_cmd_lfeed はコマンド終了までずっとブロックしてしまうのではなく、ループの中で1行受け取ったら yield 、つまりそのループをいったん停止し、そのときの結果を返す。
「次よこせ」と言われたら再開して、次の結果を返す……というように動作する。
この例だと結局は res_cmd_no_lfeed で Arrayが全部完成するまで待つので意味ないんだけどね。
Webアプリだったら、どんどんブラウザに応答を返しちゃうとか、できる。
なにがうれしいのか
- シェルコマンドが無限に出力を生成するような場合でも、その途中までの結果を受け取れる。
- シェルコマンドの実行に時間がかかる場合でも、途中までの結果を早期に受け取れる。
- 一度に作業する対象が小さいので、ワーキングメモリが最小限で済む。
- 後半で失敗したとしても、途中までの結果を返しちゃってるので、取り返しがつかない。うれしくない
forevertime.bat
@echo off
:TOP
echo %time%
goto top
こんなのを実行してみるとわかる。このバッチファイルは無限に出力しつづけるので、前者のソースだと readlines() で待ち続けてしまうが、後者は結果をどんどん取り込んで処理する。「ちぎっては投げ」のように。
注意。パイプにはバッファがある
- シェルコマンドの結果を受け取ってやらないと、シェルコマンド側が出力が詰まってしまって停まることがある。Windows10で試したところ、12kb程度のバッファが存在している模様。受け取るほうがのんびりしてると、タイムスタンプが10秒飛ぶ――みたいな現象として観測できる。
forevertime.bat(大量出力版)
@echo off
:TOP
set /P X=0123456789012345678901234567890123456789012345678901234567890123<NUL
set /P X=0123456789012345678901234567890123456789012345678901234567890123<NUL
set /P X=0123456789012345678901234567890123456789012345678901234567890123<NUL
set /P X=0123456789012345678901234567890123456789012345678901234567890123<NUL
set /P X=0123456789012345678901234567890123456789012345678901234567890123<NUL
set /P X=0123456789012345678901234567890123456789012345678901234567890123<NUL
set /P X=0123456789012345678901234567890123456789012345678901234567890123<NUL
set /P X=0123456789012345678901234567890123456789012345678901234567890123<NUL
echo %time%
goto top
もっともっとチューニングできそうだけれど、まあ、このへんにしておくか。