1. ホーム
  2. python

[解決済み] シェルコマンドの実行と出力のキャプチャ

2022-03-17 02:16:17

質問

シェルコマンドを実行し、その出力を返す関数を書きたい 文字列として エラーメッセージでも成功メッセージでもかまいません。私はただ、コマンドラインで得られたであろうのと同じ結果を得たいだけです。

そのようなことをするコード例はどのようなものでしょうか?

例えば

def run_command(cmd):
    # ??????

print run_command('mysqladmin create test -uroot -pmysqladmin12')
# Should output something like:
# mysqladmin: CREATE DATABASE failed; error: 'Can't create database 'test'; database exists'

解決方法は?

公式にメンテナンスされているすべてのバージョンの Python において、最もシンプルなアプローチは subprocess.check_output 関数を使用します。

>>> subprocess.check_output(['ls', '-l'])
b'total 0\n-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files\n'

check_output は、入力として引数のみを受け取る単一のプログラムを実行します。 1 にプリントされた通りの結果を返します。 stdout . に入力を書き込む必要がある場合 stdin にスキップして run または Popen セクションを作成します。複雑なシェルコマンドを実行したい場合は、以下の説明を参照してください。 shell=True は、この回答の最後にあります。

check_output 関数は、公式にメンテナンスされているすべてのバージョンのPythonで動作します。しかし、より最近のバージョンでは、より柔軟なアプローチが可能です。

Pythonの最新バージョン(3.5以上)です。 run

を使用している場合 Python 3.5+ で、かつ 後方互換性が必要ない は、新しい run 関数は、ほとんどのタスクで公式ドキュメントに推奨されています。これは、非常に一般的で高レベルな API を subprocess モジュールを使用します。プログラムの出力をキャプチャするには subprocess.PIPE フラグを stdout キーワード引数を指定します。次に stdout 属性が返されます。 CompletedProcess オブジェクトを作成します。

>>> import subprocess
>>> result = subprocess.run(['ls', '-l'], stdout=subprocess.PIPE)
>>> result.stdout
b'total 0\n-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files\n'

戻り値は bytes オブジェクトを作成します。もし適切な文字列が欲しい場合は decode それを 呼び出されたプロセスがUTF-8でエンコードされた文字列を返すと仮定します。

>>> result.stdout.decode('utf-8')
'total 0\n-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files\n'

必要であれば、これをすべて1行に圧縮することができる。

>>> subprocess.run(['ls', '-l'], stdout=subprocess.PIPE).stdout.decode('utf-8')
'total 0\n-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files\n'

入力を渡したい場合は、プロセスの stdin を渡すことができます。 bytes オブジェクトを input キーワード引数を指定します。

>>> cmd = ['awk', 'length($0) > 5']
>>> ip = 'foo\nfoofoo\n'.encode('utf-8')
>>> result = subprocess.run(cmd, stdout=subprocess.PIPE, input=ip)
>>> result.stdout.decode('utf-8')
'foofoo\n'

を渡すことで、エラーを捕捉することができます。 stderr=subprocess.PIPE (キャプチャは result.stderr ) または stderr=subprocess.STDOUT (にキャプチャー)。 result.stdout を通常の出力と一緒に出力します)。もし run がゼロ以外の終了コードを返したときに例外を投げるようにするには、 プロセスに check=True . (または returncode 属性の result 上記) セキュリティが心配でない場合、より複雑なシェルコマンドを実行するために shell=True この回答の最後で説明されているように

Pythonの後のバージョンでは、上記がさらに効率化されます。Python 3.7+では、上記のワンライナーは次のように綴ることができます。

>>> subprocess.run(['ls', '-l'], capture_output=True, text=True).stdout
'total 0\n-rw-r--r--  1 memyself  staff  0 Mar 14 11:04 files\n'

使用方法 run この方法では、以前の方法に比べてほんの少し複雑さが増します。しかし、これで、必要なことはほとんど何でも run 関数だけです。

古いバージョンのPython (3-3.4):詳細はこちら check_output

古いバージョンの Python を使っている場合、あるいは適度な後方互換性が必要な場合は check_output 関数は、上記で簡単に説明したように これは Python 2.7 以降で利用可能です。

subprocess.check_output(*popenargs, **kwargs)  

と同じ引数を取ります。 Popen (下記参照) そして、プログラムの出力を含む文字列を返します。この回答の冒頭に、より詳細な使用例があります。Python 3.5+の場合。 check_output を実行することと同じです。 runcheck=Truestdout=PIPE を返します。 stdout 属性で指定します。

を渡すことができます。 stderr=subprocess.STDOUT を使用すると、返される出力にエラーメッセージが含まれるようになります。セキュリティが心配でないときは、より複雑なシェルコマンドを実行することもできます。 shell=True この回答の最後に説明されているように

からのパイプが必要な場合 stderr またはプロセスへの入力を渡します。 check_output は、このタスクに対応できません。このような場合は Popen の例は、そのような場合です。

複雑なアプリケーションとレガシーバージョンのPython(2.6以下)。 Popen

深い後方互換性が必要な場合、あるいは check_output または run を提供する場合は、直接 Popen これはサブプロセス用の低レベル API をカプセル化したものです。

Popen コンストラクタは 単一のコマンド 引数なし、または リスト コマンドを最初の項目として含み、その後に任意の数の引数が続き、それぞれがリストの個別の項目となる。 shlex.split は、文字列を適切にフォーマットされたリストにパースするのに役立ちます。 Popen オブジェクトは ホストのさまざまな引数 プロセスのIO管理および低レベルの設定に使用されます。

入力を送信し、出力をキャプチャすること。 communicate は、ほとんどの場合、好ましい方法です。というように。

output = subprocess.Popen(["mycmd", "myarg"], 
                          stdout=subprocess.PIPE).communicate()[0]

または

>>> import subprocess
>>> p = subprocess.Popen(['ls', '-a'], stdout=subprocess.PIPE, 
...                                    stderr=subprocess.PIPE)
>>> out, err = p.communicate()
>>> print out
.
..
foo

を設定した場合 stdin=PIPE , communicate を経由してプロセスにデータを渡すこともできます。 stdin :

>>> cmd = ['awk', 'length($0) > 5']
>>> p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
...                           stderr=subprocess.PIPE,
...                           stdin=subprocess.PIPE)
>>> out, err = p.communicate('foo\nfoofoo\n')
>>> print out
foofoo

備考 Aaron Hallの回答 を設定する必要があることを示唆しています。 stdout , stderr および stdin をすべて PIPE (または DEVNULL を取得するために communicate が全く動作しない。

まれに、複雑なリアルタイム出力キャプチャが必要な場合があります。 バルテック の回答は、今後の方向性を示唆していますが、「Select」以外の方法は、「Select」です。 communicate は、慎重に使用しないとデッドロックになりやすい。

上記の関数と同様に、セキュリティが気にならない場合は shell=True .

備考

1. シェルコマンドの実行 shell=True 引数

通常、各呼び出しは run , check_output または Popen コンストラクタは 単一プログラム . つまり、派手なバッシュスタイルのパイプはありません。もし、複雑なシェルコマンドを実行したい場合は shell=True この3つの関数はすべてサポートしています。例えば

>>> subprocess.check_output('cat books/* | wc', shell=True, text=True)
' 1299377 17005208 101299376\n'

しかし、このようにすると セキュリティ上の懸念 . 軽いスクリプト以上のことをするのであれば、各プロセスを別々に呼び出し、それぞれの出力を入力として次のプロセスに渡す方が良いでしょう。

run(cmd, [stdout=etc...], input=other_output)

または

Popen(cmd, [stdout=etc...]).communicate(other_output)

パイプを直接つなげる誘惑は強いですが、それに打ち勝ってください。そうしないと、デッドロックが発生したり、次のような面倒なことをしなければならなくなる可能性があります。 これ .