Untuk sedikit memperluas jawaban sebelumnya di sini, ada sejumlah detail yang biasanya diabaikan.
- Lebih
subprocess.run()lebih subprocess.check_call()dan teman-teman selama subprocess.call()lebih subprocess.Popen()lebih os.system()lebihos.popen()
- Pahami dan mungkin gunakan
text=True, alias universal_newlines=True.
- Memahami arti
shell=Trueatau shell=Falsedan bagaimana hal itu mengubah penawaran dan ketersediaan kenyamanan shell.
- Memahami perbedaan antara
shdan Bash
- Memahami bagaimana suatu subproses terpisah dari induknya, dan umumnya tidak dapat mengubah induknya.
- Hindari menjalankan interpreter Python sebagai subproses dari Python.
Topik-topik ini dibahas lebih rinci di bawah ini.
Lebih suka subprocess.run()atausubprocess.check_call()
Itu subprocess.Popen() Fungsi adalah pekerja keras tingkat rendah tetapi sulit untuk digunakan dengan benar dan Anda berakhir copy / paste beberapa baris kode ... yang nyaman sudah ada di perpustakaan standar sebagai satu set tingkat yang lebih tinggi wrapper fungsi untuk berbagai keperluan, yang disajikan secara lebih rinci sebagai berikut.
Berikut paragraf dari dokumentasi :
Pendekatan yang disarankan untuk menjalankan subproses adalah menggunakan run()fungsi untuk semua kasus penggunaan yang dapat ditangani. Untuk kasus penggunaan yang lebih lanjut, Popenantarmuka yang mendasarinya dapat digunakan secara langsung.
Sayangnya, ketersediaan fungsi pembungkus ini berbeda antara versi Python.
subprocess.run()secara resmi diperkenalkan di Python 3.5. Ini dimaksudkan untuk mengganti semua yang berikut ini.
subprocess.check_output()diperkenalkan di Python 2.7 / 3.1. Ini pada dasarnya setara dengansubprocess.run(..., check=True, stdout=subprocess.PIPE).stdout
subprocess.check_call()diperkenalkan dalam Python 2.5. Ini pada dasarnya setara dengansubprocess.run(..., check=True)
subprocess.call()diperkenalkan dengan Python 2.4 dalam subprocessmodul asli ( PEP-324 ). Ini pada dasarnya setara dengansubprocess.run(...).returncode
API tingkat tinggi vs subprocess.Popen()
Refactored dan extended subprocess.run()lebih logis dan lebih fleksibel daripada fungsi lawas yang digantikannya. Ini mengembalikan CompletedProcessobjek yang memiliki berbagai metode yang memungkinkan Anda untuk mengambil status keluar, output standar, dan beberapa hasil lainnya dan indikator status dari proses yang selesai.
subprocess.run()adalah cara untuk pergi jika Anda hanya perlu program untuk menjalankan dan mengembalikan kontrol ke Python. Untuk skenario yang lebih terlibat (proses latar belakang, mungkin dengan I / O interaktif dengan program induk Python) Anda masih perlu menggunakan subprocess.Popen()dan mengurus semua pipa ledeng sendiri. Ini membutuhkan pemahaman yang cukup rumit tentang semua bagian yang bergerak dan tidak boleh dianggap enteng. PopenObjek yang lebih sederhana mewakili proses (mungkin masih berjalan) yang perlu dikelola dari kode Anda untuk sisa masa hidup dari subproses.
Mungkin harus ditekankan bahwa subprocess.Popen()sekadar menciptakan proses. Jika Anda membiarkannya di situ, Anda memiliki subproses yang berjalan bersamaan dengan Python, jadi proses "latar belakang". Jika tidak perlu melakukan input atau output atau berkoordinasi dengan Anda, itu dapat melakukan pekerjaan yang berguna secara paralel dengan program Python Anda.
Hindari os.system()danos.popen()
Sejak waktu abadi (well, sejak Python 2.5) osdokumentasi modul telah berisi rekomendasi untuk dipilih lebih subprocessdari os.system():
The subprocessModul menyediakan fasilitas yang lebih kuat untuk pemijahan proses baru dan mengambil hasil mereka; menggunakan modul itu lebih baik daripada menggunakan fungsi ini.
Masalah dengan system()itu jelas tergantung pada sistem dan tidak menawarkan cara untuk berinteraksi dengan subproses. Ini hanya berjalan, dengan output standar dan kesalahan standar di luar jangkauan Python. Satu-satunya informasi yang Python terima kembali adalah status keluar dari perintah (nol berarti sukses, meskipun makna nilai-nilai bukan nol juga agak bergantung pada sistem).
PEP-324 (yang telah disebutkan di atas) berisi alasan yang lebih terperinci mengapa os.systemmasalah dan bagaimana subprocessupaya untuk menyelesaikan masalah tersebut.
os.popen()dulu bahkan lebih kuat tidak dianjurkan :
Tidak digunakan lagi sejak versi 2.6: Fungsi ini sudah usang. Gunakan subprocessmodul.
Namun, sejak suatu saat di Python 3, telah diterapkan kembali hanya dengan menggunakan subprocess, dan mengarahkan kembali kesubprocess.Popen() dokumentasi untuk detail.
Pahami dan biasanya gunakan check=True
Anda juga akan melihat bahwa subprocess.call()ada banyak keterbatasan yang sama dengan os.system(). Dalam penggunaan reguler, Anda biasanya harus memeriksa apakah proses selesai dengan sukses, mana subprocess.check_call()dan subprocess.check_output()apakah (di mana yang terakhir juga mengembalikan output standar dari subproses yang selesai). Demikian pula, Anda harus biasanya menggunakan check=Truedengan subprocess.run()kecuali Anda secara khusus perlu untuk memungkinkan subproses untuk mengembalikan status kesalahan.
Dalam praktiknya, dengan check=Trueatau subprocess.check_*, Python akan mengeluarkan CalledProcessErrorpengecualian jika subproses mengembalikan status keluar yang bukan nol.
Kesalahan umum subprocess.run()adalah dengan menghilangkancheck=True dan terkejut ketika kode hilir gagal jika subproses gagal.
Di sisi lain, masalah umum dengan check_call()dan check_output()adalah bahwa pengguna yang secara buta menggunakan fungsi-fungsi ini terkejut ketika pengecualian muncul misalnya ketika greptidak menemukan kecocokan. (Anda mungkin harus mengganti grepdengan kode Python asli, seperti diuraikan di bawah ini.)
Semua hal diperhitungkan, Anda perlu memahami bagaimana perintah shell mengembalikan kode keluar, dan dalam kondisi apa mereka akan mengembalikan kode keluar non-nol (kesalahan), dan membuat keputusan sadar bagaimana tepatnya harus ditangani.
Memahami dan mungkin menggunakan text=Truealiasuniversal_newlines=True
Sejak Python 3, string internal ke Python adalah string Unicode. Tetapi tidak ada jaminan bahwa suatu proses menghasilkan keluaran Unicode, atau string sama sekali.
(Jika perbedaannya tidak segera jelas, Unicode Pragmatis Ned Batchelder direkomendasikan, jika tidak secara langsung wajib, membaca. Ada presentasi video 36 menit di belakang tautan jika Anda mau, meskipun membaca halaman sendiri mungkin akan memakan waktu lebih sedikit. )
Jauh di lubuk hati, Python harus mengambil bytesbuffer dan menafsirkannya entah bagaimana. Jika itu berisi gumpalan data biner, itu tidak boleh diterjemahkan ke dalam string Unicode, karena itu perilaku yang rawan kesalahan dan bug - tepatnya jenis perilaku sial yang membingungkan banyak skrip Python 2, sebelum ada cara untuk benar membedakan antara teks yang disandikan dan data biner.
Dengan text=True, Anda memberi tahu Python bahwa Anda, pada kenyataannya, mengharapkan kembali data tekstual dalam pengkodean default sistem, dan bahwa itu harus diterjemahkan ke dalam string Python (Unicode) ke yang terbaik dari kemampuan Python (biasanya UTF-8 pada tingkat sedang hingga sistem tanggal, kecuali mungkin Windows?)
Jika bukan itu yang Anda minta kembali, Python hanya akan memberi Anda bytesstring di stdoutdan stderrstring. Mungkin di beberapa kemudian mengarahkan Anda lakukan tahu bahwa mereka string teks setelah semua, dan kau tahu pengkodean mereka. Kemudian, Anda dapat memecahkan kode mereka.
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 memperkenalkan alias yang lebih pendek dan lebih deskriptif dan dapat dipahami textuntuk argumen kata kunci yang sebelumnya agak menyesatkan universal_newlines.
Pahami shell=Truevsshell=False
Dengan shell=TrueAnda memberikan satu string ke shell Anda, dan shell mengambilnya dari sana.
Dengan shell=FalseAnda melewati daftar argumen ke OS, melewati shell.
Ketika Anda tidak memiliki shell, Anda menyimpan sebuah proses dan menyingkirkan sejumlah kompleksitas tersembunyi yang cukup besar, yang mungkin atau mungkin tidak mengandung bug atau bahkan masalah keamanan.
Di sisi lain, ketika Anda tidak memiliki shell, Anda tidak memiliki redirection, ekspansi wildcard, kontrol pekerjaan, dan sejumlah besar fitur shell lainnya.
Kesalahan umum adalah menggunakan shell=Truedan kemudian masih memberikan Python daftar token, atau sebaliknya. Ini terjadi pada beberapa kasus, tetapi benar-benar tidak jelas dan bisa pecah dengan cara yang menarik.
# 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)
Retort yang umum "tapi itu bekerja untuk saya" bukanlah sanggahan yang berguna kecuali jika Anda mengerti persis dalam keadaan apa itu bisa berhenti bekerja.
Contoh Refactoring
Sangat sering, fitur shell dapat diganti dengan kode Python asli. Awk sederhana ataused skrip sederhana mungkin seharusnya hanya diterjemahkan ke Python saja.
Untuk mengilustrasikannya secara parsial, berikut adalah contoh tipikal tetapi sedikit konyol yang melibatkan banyak fitur shell.
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))
Beberapa hal yang perlu diperhatikan di sini:
- Dengan
shell=FalseAnda tidak perlu mengutip bahwa shell membutuhkan sekitar string. Menempatkan tanda kutip mungkin kesalahan.
- Seringkali masuk akal untuk menjalankan kode sesedikit mungkin dalam suatu subproses. Ini memberi Anda lebih banyak kontrol atas eksekusi dari dalam kode Python Anda.
- Karena itu, jaringan pipa shell yang rumit membosankan dan kadang-kadang menantang untuk diterapkan kembali dengan Python.
Kode refactored juga menggambarkan seberapa banyak shell benar-benar bekerja untuk Anda dengan sintaks yang sangat singkat - baik atau buruk. Python mengatakan eksplisit adalah lebih baik daripada implisit tetapi kode python adalah agak verbose dan bisa dibilang terlihat lebih kompleks daripada ini benar-benar. Di sisi lain, ia menawarkan sejumlah poin di mana Anda dapat mengambil kendali di tengah-tengah sesuatu yang lain, sebagaimana dicontohkan oleh peningkatan yang kita dapat dengan mudah memasukkan nama host bersama dengan output perintah shell. (Ini sama sekali tidak menantang untuk dilakukan di shell, tetapi dengan mengorbankan pengalihan lain dan mungkin proses lain.)
Konstruksi Shell Umum
Untuk kelengkapan, berikut adalah penjelasan singkat tentang beberapa fitur shell ini, dan beberapa catatan tentang bagaimana mereka dapat diganti dengan fasilitas Python asli.
- Globbing alias ekspansi wildcard dapat diganti dengan
glob.glob()atau sangat sering dengan perbandingan string Python sederhana seperti for file in os.listdir('.'): if not file.endswith('.png'): continue. Bash memiliki berbagai fasilitas ekspansi lain seperti .{png,jpg}ekspansi brace dan {1..100}juga ekspansi tilde ( ~memperluas ke direktori home Anda, dan lebih umum ~accountke direktori home dari pengguna lain)
- Variabel Shell suka
$SHELLatau $my_exported_varkadang-kadang bisa dengan mudah diganti dengan variabel Python. Variabel shell yang diekspor tersedia sebagai misalnya os.environ['SHELL'](artinya exportadalah membuat variabel tersedia untuk subproses - variabel yang tidak tersedia untuk subproses jelas tidak akan tersedia untuk Python berjalan sebagai subproses shell, atau sebaliknya. Kata env=kunci argumen ke subprocessmetode memungkinkan Anda untuk mendefinisikan lingkungan subproses sebagai kamus, jadi itu salah satu cara untuk membuat variabel Python terlihat oleh sebuah subproses). Dengan shell=FalseAnda akan perlu memahami cara menghapus tanda kutip; misalnya, cd "$HOME"setara denganos.chdir(os.environ['HOME']) tanpa tanda kutip di sekitar nama direktori. (Sangat seringcdtidak berguna atau tidak diperlukan, dan banyak pemula menghilangkan tanda kutip ganda di sekitar variabel dan lolos sampai suatu hari ... )
- Pengalihan memungkinkan Anda untuk membaca dari file sebagai input standar Anda, dan menulis output standar Anda ke file.
grep 'foo' <inputfile >outputfileterbuka outputfileuntuk menulis dan inputfilemembaca, dan meneruskan isinya sebagai input standar grep, yang kemudian menjadi standar keluaran outputfile. Ini biasanya tidak sulit untuk diganti dengan kode Python asli.
- Pipa adalah bentuk pengalihan.
echo foo | nlmenjalankan dua subproses, di mana output standar echoadalah input standar nl(pada tingkat OS, dalam sistem mirip Unix, ini adalah satu pegangan file). Jika Anda tidak dapat mengganti satu atau kedua ujung pipa dengan kode Python asli, mungkin berpikir tentang menggunakan shell setelah semua, terutama jika pipa memiliki lebih dari dua atau tiga proses (meskipun melihat pipesmodul di pustaka standar Python atau angka pesaing pihak ketiga yang lebih modern dan serbaguna).
- Kontrol pekerjaan memungkinkan Anda menyela pekerjaan, menjalankannya di latar belakang, mengembalikannya ke latar depan, dll. Sinyal Unix dasar untuk berhenti dan melanjutkan proses tentu saja juga tersedia dari Python. Tetapi pekerjaan adalah abstraksi level yang lebih tinggi dalam shell yang melibatkan kelompok proses dll yang harus Anda pahami jika Anda ingin melakukan sesuatu seperti ini dari Python.
- Mengutip di shell berpotensi membingungkan sampai Anda memahami bahwa semuanya pada dasarnya adalah sebuah string. Jadi
ls -l /sama dengan 'ls' '-l' '/'tetapi mengutip sekitar literal sepenuhnya opsional. String yang tidak dikutip yang mengandung karakter meta shell menjalani ekspansi parameter, tokenization whitespace, dan ekspansi wildcard; Kutipan ganda mencegah tokenization whitespace dan ekspansi wildcard tetapi memungkinkan ekspansi parameter (substitusi variabel, substitusi perintah, dan pemrosesan backslash). Ini sederhana dalam teori tetapi bisa membingungkan, terutama ketika ada beberapa lapisan interpretasi (perintah shell jarak jauh, misalnya).
Memahami perbedaan antara shdan Bash
subprocessmenjalankan perintah shell /bin/shAnda kecuali Anda secara khusus meminta sebaliknya (kecuali tentu saja pada Windows, di mana ia menggunakan nilai COMSPECvariabel). Ini berarti bahwa berbagai fitur Bash-only seperti array, [[dll tidak tersedia.
Jika Anda perlu menggunakan sintaks Bash-only, Anda dapat meneruskan jalur ke shell sebagai executable='/bin/bash'(di mana tentu saja jika Bash Anda diinstal di tempat lain, Anda perlu menyesuaikan jalur).
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 subprocessterpisah dari induknya, dan tidak dapat mengubahnya
Kesalahan yang agak umum adalah melakukan sesuatu seperti
subprocess.run('foo=bar', shell=True)
subprocess.run('echo "$foo"', shell=True) # Doesn't work
yang terlepas dari kurangnya keanggunan juga mengkhianati kurangnya pemahaman mendasar tentang "sub" bagian dari nama "subproses".
Proses anak berjalan sepenuhnya terpisah dari Python, dan ketika selesai, Python tidak tahu apa yang dilakukannya (terlepas dari indikator samar yang dapat disimpulkan dari status keluar dan output dari proses anak). Seorang anak umumnya tidak dapat mengubah lingkungan orang tua; itu tidak dapat mengatur variabel, mengubah direktori kerja, atau, dalam banyak kata, berkomunikasi dengan orang tuanya tanpa kerja sama dari orang tua.
Perbaikan langsung dalam kasus khusus ini adalah menjalankan kedua perintah dalam satu proses tunggal;
subprocess.run('foo=bar; echo "$foo"', shell=True)
meskipun jelas ini kasus penggunaan khusus tidak memerlukan shell sama sekali. Ingat, Anda dapat memanipulasi lingkungan proses saat ini (dan juga anak-anaknya) melalui
os.environ['foo'] = 'bar'
atau melewati pengaturan lingkungan ke proses anak dengan
subprocess.run('echo "$foo"', shell=True, env={'foo': 'bar'})
(belum lagi refactoring yang jelas subprocess.run(['echo', 'bar']); tetapi echomerupakan contoh yang buruk dari sesuatu untuk dijalankan di subproses di tempat pertama, tentu saja).
Jangan jalankan Python dari Python
Ini sedikit nasihat yang meragukan; pasti ada situasi di mana itu masuk akal atau bahkan merupakan persyaratan mutlak untuk menjalankan interpreter Python sebagai subproses dari skrip Python. Tetapi sangat sering, pendekatan yang benar adalah hanya dengan importmodul Python lain ke dalam skrip panggilan Anda dan memanggil fungsinya secara langsung.
Jika skrip Python lain berada di bawah kendali Anda, dan itu bukan modul, pertimbangkan untuk mengubahnya menjadi satu . (Jawaban ini sudah terlalu lama jadi saya tidak akan membahas detailnya di sini.)
Jika Anda membutuhkan paralelisme, Anda dapat menjalankan fungsi Python di subproses dengan multiprocessingmodul. Ada juga threadingyang menjalankan banyak tugas dalam satu proses (yang lebih ringan dan memberi Anda lebih banyak kontrol, tetapi juga lebih dibatasi bahwa utas dalam suatu proses digabungkan secara ketat, dan terikat pada satu GIL .)
cwm. Mungkin Anda memiliki beberapa konfigurasi di lingkungan Anda.bashrcyang mengatur lingkungan untuk penggunaan bash interaktif?