ここで以前の回答をいくらか拡大するために、一般に見落とされている詳細がいくつかあります。
- 好む
subprocess.run()
以上subprocess.check_call()
にわたり、友人subprocess.call()
オーバーsubprocess.Popen()
オーバーos.system()
オーバーos.popen()
- 理解し、おそらく使用
text=True
別名、universal_newlines=True
。
- 意味を理解し
shell=True
たりshell=False
、どのようにそれがシェルの便利の可用性を引用して変更を。
sh
とBashの違いを理解する
- サブプロセスがその親からどのように分離されているかを理解し、通常は親を変更できません。
- PythonインタープリターをPythonのサブプロセスとして実行しないでください。
これらのトピックについては、以下で詳しく説明します。
優先subprocess.run()
またはsubprocess.check_call()
subprocess.Popen()
、便利、すでに様々な目的のために、より高いレベルのラッパー関数の集合として標準ライブラリに存在する...機能は、低レベルの主力であるが、正しく使用するのが難しいですし、複数行のコードを貼り付ける/コピーを終わりますこれについては、以下で詳しく説明します。
ここにドキュメントの段落があります:
サブプロセスを呼び出すための推奨されるアプローチは、run()
処理できるすべてのユースケースで関数を使用することです。より高度な使用例では、基になるPopen
インターフェースを直接使用できます。
残念ながら、これらのラッパー関数の可用性はPythonバージョン間で異なります。
subprocess.run()
Python 3.5で正式に導入されました。以下のすべてを置き換えることを意味します。
subprocess.check_output()
Python 2.7 / 3.1で導入されました。基本的にはsubprocess.run(..., check=True, stdout=subprocess.PIPE).stdout
subprocess.check_call()
Python 2.5で導入されました。基本的にはsubprocess.run(..., check=True)
subprocess.call()
Python 2.4の元のsubprocess
モジュール(PEP-324)で導入されました。基本的にはsubprocess.run(...).returncode
高レベルAPIと subprocess.Popen()
リファクタリングおよび拡張subprocess.run()
は、それが置き換える古いレガシー機能よりも論理的で用途が広いです。CompletedProcess
終了ステータス、標準出力、およびその他のいくつかの結果と、終了したサブプロセスからのステータスインジケーターを取得できるさまざまなメソッドを持つオブジェクトを返します。
subprocess.run()
実行して制御をPythonに戻すだけのプログラムが必要な場合に使用する方法です。より複雑なシナリオ(バックグラウンドプロセス、おそらくPythonの親プログラムとの対話型I / Oを使用)の場合も、使用subprocess.Popen()
してすべての配管を自分で処理する必要があります。これには、すべての可動部分のかなり複雑な理解が必要であり、軽く行うべきではありません。より単純なPopen
オブジェクトは、サブプロセスの存続期間の残りの間、コードから管理する必要がある(おそらく実行中の)プロセスを表します。
subprocess.Popen()
単にプロセスを作成するだけであることをおそらく強調する必要があります。そのままにしておくと、Pythonと同時にサブプロセスが実行されるため、「バックグラウンド」プロセスになります。入力や出力を行う必要がない場合、または調整する必要がない場合は、Pythonプログラムと並行して有用な作業を行うことができます。
避けos.system()
てos.popen()
永遠に(まあ、Python 2.5以降)os
モジュールのドキュメントには、次のような推奨事項が含まsubprocess
れていos.system()
ます。
このsubprocess
モジュールは、新しいプロセスを生成して結果を取得するためのより強力な機能を提供します。この関数を使用するよりも、そのモジュールを使用する方が適切です。
の問題system()
は、明らかにシステムに依存しており、サブプロセスと対話する方法を提供していないことです。単純に実行され、標準出力と標準エラーはPythonの範囲外です。Pythonが受け取る唯一の情報は、コマンドの終了ステータスです(ゼロは成功を意味しますが、ゼロ以外の値の意味もシステムによって多少異なります)。
PEP-324(すでに上で説明されています)には、なぜos.system
問題があるのか、そしてsubprocess
それらの問題をどのように解決しようとするのかについてのより詳細な根拠が含まれています。
os.popen()
かつてはより強く推奨されていませんでした:
バージョン2.6で撤廃:この関数は廃止されました。subprocess
モジュールを使用します。
ただし、Python 3のある時点から、単にを使用するようsubprocess
に再実装され、subprocess.Popen()
詳細についてはドキュメントにリダイレクトされます。
理解して通常使用する check=True
またsubprocess.call()
、と同じ制限の多くがあることに気づくでしょうos.system()
。通常の使用では、一般的にプロセスが正常に終了したかどうかを確認し、その必要があるsubprocess.check_call()
とsubprocess.check_output()
(後者はまた、完成したサブプロセスの標準出力を返す場合)を行います。同様に、サブプロセスがエラーステータスを返すことを特に許可する必要がない限り、通常はcheck=True
withを使用するsubprocess.run()
必要があります。
実際には、check=True
またはsubprocess.check_*
を使用すると、サブプロセスがゼロ以外の終了ステータスを返す場合、PythonはCalledProcessError
例外をスローします。
の一般的なエラーsubprocess.run()
はcheck=True
、サブプロセスが失敗した場合、ダウンストリームコードが省略されて驚いたことです。
一方、一般的な問題check_call()
とcheck_output()
するときに例外が例えば上げた時に盲目的にこれらの機能を使用するユーザーが驚いたということでしたgrep
マッチを見つけることができませんでした。(grep
とにかく、以下で説明するように、おそらくネイティブPythonコードに置き換える必要があります。)
カウントされたすべてのものは、シェルコマンドがどのように終了コードを返すかを理解し、どのような条件下で非ゼロ(エラー)の終了コードを返すかを理解し、どのように処理するかを意識的に決定する必要があります。
理解し、おそらくtext=True
別名を使用しますuniversal_newlines=True
Python 3以降、Python内部の文字列はUnicode文字列です。ただし、サブプロセスがUnicode出力または文字列を生成するという保証はありません。
(違いがすぐにわからない場合は、Ned BatchelderのPragmatic Unicodeをお勧めします。義務ではないので、読むことをお勧めします。必要に応じて、リンクの後ろに36分のビデオプレゼンテーションがありますが、自分でページを読むのにかかる時間は大幅に短縮されます。 )
深く掘り下げると、Pythonはbytes
バッファをフェッチして、なんとかして解釈する必要があります。バイナリデータのblobが含まれている場合、それはエラーが発生しやすく、バグを誘発する動作であるため、Unicode文字列にデコードしないでください。正確に、多くのPython 2スクリプトをだらだらするような厄介な動作です。エンコードされたテキストとバイナリデータを適切に区別します。
を使用text=True
すると、実際にはシステムのデフォルトのエンコーディングでテキストデータを予期し、Pythonの能力を最大限に発揮するようにPython(Unicode)文字列にデコードする必要があることをPythonに伝えます(通常、適度に日付システム、おそらくWindowsを除いて?)
それがならないあなたが戻って要求するものを、Pythonはあなたに与えるbytes
内の文字列stdout
とstderr
文字列を。たぶん、いくつかの後の時点で、あなたはない、彼らはすべての後にテキスト文字列だったことを知って、あなたは自分のエンコーディングを知っています。その後、それらをデコードできます。
normal = subprocess.run([external, arg],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=True,
text=True)
print(normal.stdout)
convoluted = subprocess.run([external, arg],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
check=True)
# You have to know (or guess) the encoding
print(convoluted.stdout.decode('utf-8'))
Python 3.7では、text
以前はやや誤解を招くように呼ばれていたキーワード引数に、より短くて説明的で理解しやすいエイリアスが導入されましたuniversal_newlines
。
理解shell=True
対shell=False
ではshell=True
、あなたのシェルに単一の文字列を渡すと、シェルはそこからそれを取ります。
ではshell=False
、あなたのシェルをバイパスし、OSに引数のリストを渡します。
シェルがない場合は、プロセスを保存して、かなりの量の隠れた複雑さを取り除きます。これにより、バグやセキュリティの問題が発生することもあれば、発生しないこともあります。
一方、シェルがない場合、リダイレクト、ワイルドカード拡張、ジョブ制御、およびその他の多数のシェル機能はありません。
よくある間違いは、shell=True
Pythonにトークンのリストを使用してから渡すこと、またはその逆です。これは場合によってはうまくいきますが、実際には不明確であり、興味深い方法で壊れる可能性があります。
# XXX AVOID THIS BUG
buggy = subprocess.run('dig +short stackoverflow.com')
# XXX AVOID THIS BUG TOO
broken = subprocess.run(['dig', '+short', 'stackoverflow.com'],
shell=True)
# XXX DEFINITELY AVOID THIS
pathological = subprocess.run(['dig +short stackoverflow.com'],
shell=True)
correct = subprocess.run(['dig', '+short', 'stackoverflow.com'],
# Probably don't forget these, too
check=True, text=True)
# XXX Probably better avoid shell=True
# but this is nominally correct
fixed_but_fugly = subprocess.run('dig +short stackoverflow.com',
shell=True,
# Probably don't forget these, too
check=True, text=True)
一般的なレトルト「しかし、それは私にとっては機能します」は、どのような状況で機能しなくなるかを正確に理解しない限り、有用な反論ではありません。
リファクタリングの例
多くの場合、シェルの機能はネイティブのPythonコードで置き換えることができます。単純なAwkまたはsed
スクリプトは、おそらく単に代わりにPythonに変換する必要があります。
これを部分的に説明するために、ここに、多くのシェル機能が関係する典型的で少しばかげた例を示します。
cmd = '''while read -r x;
do ping -c 3 "$x" | grep 'round-trip min/avg/max'
done <hosts.txt'''
# Trivial but horrible
results = subprocess.run(
cmd, shell=True, universal_newlines=True, check=True)
print(results.stdout)
# Reimplement with shell=False
with open('hosts.txt') as hosts:
for host in hosts:
host = host.rstrip('\n') # drop newline
ping = subprocess.run(
['ping', '-c', '3', host],
text=True,
stdout=subprocess.PIPE,
check=True)
for line in ping.stdout.split('\n'):
if 'round-trip min/avg/max' in line:
print('{}: {}'.format(host, line))
ここで注意すべきいくつかの点:
- では
shell=False
、あなたのシェルは、文字列の周りに必要であることを引用する必要はありません。とにかく引用符を付けることはおそらくエラーです。
- 多くの場合、サブプロセスではできるだけ少ないコードを実行することが理にかなっています。これにより、Pythonコード内から実行をより詳細に制御できます。
- そうは言っても、複雑なシェルパイプラインは退屈で、Pythonでの再実装が困難な場合があります。
リファクタリングされたコードは、非常に簡潔な構文でシェルが実際にどの程度機能するかを示しています。Pythonは、明示的が暗黙的よりも優れていると言いますが、Pythonコードはかなり冗長であり、間違いなくこれは実際よりも複雑に見えます。一方、シェルコマンドの出力にホスト名を簡単に含めることができる拡張機能によって簡単に例示されているように、それは他の何かの途中で制御を取得できる多くのポイントを提供します。(これは、シェルで行うことも決して難しいことではありませんが、さらに別の迂回とおそらく別のプロセスを犠牲にして行われます。)
一般的なシェル構造
完全を期すために、これらのシェル機能のいくつかの簡単な説明と、おそらくネイティブPython機能でそれらを置き換える方法についてのいくつかの注意事項を示します。
- ワイルドカード拡張として知られるグロビングは、の
glob.glob()
ような単純なPython文字列比較に置き換えることができますfor file in os.listdir('.'): if not file.endswith('.png'): continue
。Bashには、.{png,jpg}
ブレース展開や{1..100}
チルダ展開(~
ホームディレクトリに展開され、より一般的~account
には別のユーザーのホームディレクトリに展開される)のような他のさまざまな展開機能があります。
$SHELL
またはのようなシェル変数は、$my_exported_var
単にPython変数に置き換えることができます。エクスポートされたシェル変数は、たとえばos.environ['SHELL']
(変数の意味はexport
、サブプロセスで変数を使用できるようにすることです-サブプロセスで使用できない変数は、シェルのサブプロセスとして実行されているPythonでは使用できません。その逆も同様です。env=
キーワードメソッドへの引数をsubprocess
使用すると、サブプロセスの環境をディクショナリとして定義できるため、Python変数をサブプロセスから見えるようにする1つの方法です)。ではshell=False
、引用符を削除する方法を理解する必要があります。たとえば、ディレクトリ名を引用符で囲まないことcd "$HOME"
と同じos.chdir(os.environ['HOME'])
です。(よくcd
とにかく便利でも必要でもありません、そして多くの初心者は変数を囲む二重引用符を省略し、1日までそれを避けます...)
- リダイレクトを使用すると、ファイルから標準入力として読み取り、標準出力をファイルに書き込むことができます。書き込み用と読み取り用に
grep 'foo' <inputfile >outputfile
開きoutputfile
、inputfile
その内容を標準入力としてに渡しgrep
、その標準出力をに渡しoutputfile
ます。これは通常、ネイティブのPythonコードで置き換えるのは難しくありません。
- パイプラインはリダイレクトの一種です。
echo foo | nl
の標準出力がecho
標準入力である2つのサブプロセスを実行しますnl
(OSレベルでは、Unixのようなシステムでは、これは単一のファイルハンドルです)。パイプラインの一方または両方の端をネイティブのPythonコードで置き換えることができない場合、特にパイプラインに2つまたは3つを超えるプロセスがある場合は、おそらくシェルの使用を検討してください(ただしpipes
、Python標準ライブラリのモジュールまたは番号を見てください)よりモダンで用途の広いサードパーティの競合他社の)。
- ジョブ制御を使用すると、ジョブを中断したり、バックグラウンドで実行したり、フォアグラウンドに戻したりできます。プロセスを停止して続行するための基本的なUnixシグナルは、もちろんPythonからも使用できます。しかし、ジョブはシェルでのより高いレベルの抽象化であり、Pythonからこのようなことをしたい場合に理解する必要があるプロセスグループなどを含みます。
- すべてが基本的に文字列であることを理解するまで、シェルでの引用は混乱を招く可能性があります。これ
ls -l /
はと同等です'ls' '-l' '/'
が、リテラルの引用は完全にオプションです。シェルのメタ文字を含む引用符で囲まれていない文字列は、パラメーターの展開、空白のトークン化、およびワイルドカードの展開を受けます。二重引用符は、空白のトークン化とワイルドカードの展開を防ぎますが、パラメーターの展開(変数置換、コマンド置換、およびバックスラッシュ処理)を許可します。これは理論的には簡単ですが、特に複数の解釈レイヤーがある場合(たとえば、リモートシェルコマンドなど)に戸惑うことがあります。
sh
とBashの違いを理解する
subprocess
/bin/sh
特に要求しない限り、シェルコマンドを実行します(もちろん、WindowsではCOMSPEC
変数の値を使用します)。つまり、配列[[
などのBash専用のさまざまな機能は利用できません。
Bashのみの構文を使用する必要がある場合は、シェルへのパスを渡すことができますexecutable='/bin/bash'
(もちろん、Bashが別の場所にインストールされている場合は、パスを調整する必要があります)。
subprocess.run('''
# This for loop syntax is Bash only
for((i=1;i<=$#;i++)); do
# Arrays are Bash-only
array[i]+=123
done''',
shell=True, check=True,
executable='/bin/bash')
A subprocess
はその親から分離されており、変更できません
やや一般的な間違いは次のようなことです
subprocess.run('foo=bar', shell=True)
subprocess.run('echo "$foo"', shell=True) # Doesn't work
エレガンスの欠如は別として、「サブプロセス」という名前の「サブ」の部分に対する理解の根本的な欠如を裏付けています。
子プロセスはPythonから完全に分離して実行され、終了すると、Pythonはそれが何をしたのかわかりません(終了ステータスと子プロセスからの出力から推測できる漠然としたインジケーターは別として)。子供は通常、親の環境を変更できません。変数を設定したり、作業ディレクトリを変更したりできません。つまり、親からの協力なしに、親と通信することはできません。
この特定のケースでの即時の修正は、単一のサブプロセスで両方のコマンドを実行することです。
subprocess.run('foo=bar; echo "$foo"', shell=True)
ただし、この特定の使用例では、シェルをまったく必要としません。を介して、現在のプロセス(およびその子)の環境を操作できます。
os.environ['foo'] = 'bar'
または環境設定を子プロセスに渡します
subprocess.run('echo "$foo"', shell=True, env={'foo': 'bar'})
(明白なリファクタリングは言うまでもありませんsubprocess.run(['echo', 'bar'])
。しかし、echo
当然の最初の場所でサブプロセスで実行するために何かの貧しい人々の一例です)。
PythonからPythonを実行しない
これは少し疑わしいアドバイスです。確かに、PythonスクリプトからサブプロセスとしてPythonインタープリターを実行することが理にかなっている、または絶対的な要件でさえある状況があります。しかし、非常に頻繁に、正しいアプローチはimport
、他のPythonモジュールを呼び出してスクリプトに組み込み、その関数を直接呼び出すことです。
他のPythonスクリプトが制御下にあり、それがモジュールではない場合は、1つに変換することを検討してください。(この回答はすでに長すぎるため、ここでは詳しく説明しません。)
並列処理が必要な場合は、multiprocessing
モジュールを使用してサブプロセスでPython関数を実行できます。threading
単一のプロセスで複数のタスクを実行するもの もあります(より軽量で制御性が向上しますが、プロセス内のスレッドが密結合され、単一のGILにバインドされるため、より制約されます)。
cwm
。多分あなたはあなたの中.bashrc
にインタラクティブbash使用のための環境を設定するいくつかの設定がありますか?