Arti sebenarnya dari 'shell = True' dalam subproses

260

Saya memanggil berbagai proses dengan subprocessmodul. Namun, saya punya pertanyaan.

Dalam kode berikut:

callProcess = subprocess.Popen(['ls', '-l'], shell=True)

dan

callProcess = subprocess.Popen(['ls', '-l']) # without shell

Keduanya bekerja. Setelah membaca dokumen, saya jadi tahu itu shell=Trueberarti mengeksekusi kode melalui shell. Jadi itu berarti saat tidak ada, prosesnya langsung dimulai.

Jadi apa yang harus saya sukai untuk kasus saya - saya perlu menjalankan proses dan mendapatkan hasilnya. Apa untungnya saya memanggilnya dari dalam shell atau di luarnya.

pengguna225312
sumber
21
perintah pertama salah: -ldilewatkan ke /bin/sh(shell) alih-alih lsprogram di Unix jikashell=True . Argumen string harus digunakan dengan shell=Truedalam banyak kasus, bukan daftar.
jfs
1
re "prosesnya langsung dimulai": Ya?
allyourcode
9
Pernyataan "Keduanya bekerja." tentang 2 panggilan itu tidak benar dan menyesatkan. Panggilan bekerja secara berbeda. Hanya beralih dari shell=Trueke Falsedan sebaliknya adalah kesalahan. Dari dokumen : "Pada POSIX dengan shell = True, (...) Jika args adalah suatu urutan, item pertama menentukan string perintah, dan setiap item tambahan akan diperlakukan sebagai argumen tambahan untuk shell itu sendiri.". Di Windows ada konversi otomatis , yang mungkin tidak diinginkan.
mbdevpl

Jawaban:

183

Manfaat tidak menelepon melalui shell adalah Anda tidak menggunakan 'program misteri'. Pada POSIX, variabel lingkungan SHELLmengontrol biner yang dipanggil sebagai "shell." Di Windows, tidak ada turunan shell bourne, hanya cmd.exe.

Jadi memohon shell memanggil program yang dipilih pengguna dan bergantung pada platform. Secara umum, hindari doa melalui shell.

Meminta melalui shell tidak memungkinkan Anda untuk memperluas variabel lingkungan dan file gumpalan sesuai dengan mekanisme shell yang biasa. Pada sistem POSIX, shell memperluas gumpalan file ke daftar file. Pada Windows, segumpal berkas (misalnya, "*. *") Tidak diperluas oleh shell, sih (tapi variabel lingkungan pada baris perintah yang diperluas oleh cmd.exe).

Jika Anda pikir Anda ingin ekspansi variabel lingkungan dan gumpalan file, telusuri ILSserangan tahun 1992-ish pada layanan jaringan yang melakukan pemanggilan subprogram melalui shell. Contohnya termasuk berbagai sendmailbackdoors yang terlibat ILS.

Singkatnya, gunakan shell=False.

Heath Hunnicutt
sumber
2
Terima kasih atas jawabannya. Meskipun saya benar-benar tidak pada tahap itu di mana saya harus khawatir tentang eksploitasi, tetapi saya mengerti apa yang Anda maksud.
user225312
55
Jika Anda ceroboh pada awalnya, tidak ada jumlah kekhawatiran yang akan membantu Anda mengejar ketinggalan nanti. ;)
Heath Hunnicutt
Bagaimana jika Anda ingin membatasi memori maksimum dari subproses? stackoverflow.com/questions/3172470/…
Pramod
8
pernyataan tentang $SHELLitu tidak benar. Mengutip subprocess.html: "Pada Unix with shell=True, shell default ke /bin/sh." (tidak $SHELL)
marcin
1
@ user2428107: Ya, jika Anda menggunakan doa backtick pada Perl, Anda menggunakan doa shell dan membuka masalah yang sama. Gunakan 3+ arg openjika Anda ingin cara aman untuk menjalankan program dan menangkap hasilnya.
ShadowRanger
137
>>> import subprocess
>>> subprocess.call('echo $HOME')
Traceback (most recent call last):
...
OSError: [Errno 2] No such file or directory
>>>
>>> subprocess.call('echo $HOME', shell=True)
/user/khong
0

Menetapkan argumen shell ke nilai sebenarnya menyebabkan subproses untuk menelurkan proses shell menengah, dan menyuruhnya untuk menjalankan perintah. Dengan kata lain, menggunakan shell perantara berarti bahwa variabel, pola glob, dan fitur shell khusus lainnya dalam string perintah diproses sebelum perintah dijalankan. Di sini, dalam contoh, $ HOME diproses sebelum perintah echo. Sebenarnya, ini adalah kasus perintah dengan ekspansi shell sementara perintah ls-l dianggap sebagai perintah sederhana.

sumber: Modul Subproses

Mina Gabriel
sumber
16
Tidak tahu mengapa ini bukan jawaban yang dipilih. Sejauh ini yang benar-benar cocok dengan pertanyaan
Rodrigo Lopez Guerra
1
setuju. ini adalah contoh yang baik bagi saya untuk memahami apa arti shell = True.
user389955
2
Mengatur argumen shell ke nilai yang benar menyebabkan subproses untuk menelurkan proses shell menengah, dan memintanya untuk menjalankan perintah Ya Tuhan ini memberitahu semuanya. Mengapa jawaban ini tidak diterima ??? Mengapa?
pouya
Saya pikir masalahnya adalah argumen pertama yang dipanggil adalah daftar, bukan string, tetapi yang memberikan kesalahan jika shell Salah. Mengubah perintah menjadi daftar akan membuat ini berhasil
Lincoln Randall McFarland
Maaf komentar saya sebelumnya pergi sebelum saya selesai. Untuk menjadi jelas: Saya sering melihat penggunaan subproses dengan shell = True dan perintahnya adalah string, misalnya 'ls -l', (Saya berharap untuk menghindari kesalahan ini) tetapi subproses mengambil daftar (dan string sebagai daftar elemen satu) . Untuk menjalankan tanpa mengeluarkan shell (dan masalah keamanan dengan itu ) gunakan daftar subprocess.call (['ls', '-l'])
Lincoln Randall McFarland
42

Contoh di mana ada yang salah dengan Shell = True ditampilkan di sini

>>> from subprocess import call
>>> filename = input("What file would you like to display?\n")
What file would you like to display?
non_existent; rm -rf / # THIS WILL DELETE EVERYTHING IN ROOT PARTITION!!!
>>> call("cat " + filename, shell=True) # Uh-oh. This will end badly...

Periksa dokumen di sini: subprocess.call ()

Richeek
sumber
6
Tautan ini sangat berguna. Seperti yang dinyatakan dalam tautan: Menjalankan perintah shell yang menggabungkan input yang tidak bersih dari sumber yang tidak dipercaya membuat program rentan terhadap injeksi shell, kelemahan keamanan serius yang dapat mengakibatkan eksekusi perintah sewenang-wenang. Untuk alasan ini, penggunaan shell = True sangat tidak disarankan dalam kasus di mana string perintah dibangun dari input eksternal.
jtuki
39

Menjalankan program melalui shell berarti bahwa semua input pengguna yang diteruskan ke program ditafsirkan sesuai dengan sintaks dan aturan semantik dari shell yang dipanggil. Paling-paling, ini hanya menyebabkan ketidaknyamanan bagi pengguna, karena pengguna harus mematuhi aturan-aturan ini. Misalnya, jalur yang berisi karakter shell khusus seperti tanda kutip atau kosong harus diloloskan. Paling buruk, itu menyebabkan kebocoran keamanan, karena pengguna dapat menjalankan program sewenang-wenang.

shell=Truekadang-kadang nyaman untuk menggunakan fitur shell tertentu seperti pemisahan kata atau ekspansi parameter. Namun, jika fitur seperti itu diperlukan, manfaatkan modul lain yang diberikan kepada Anda (misalnya os.path.expandvars()untuk perluasan parameter atau shlexuntuk pemisahan kata). Ini berarti lebih banyak pekerjaan, tetapi menghindari masalah lain.

Singkatnya: Hindari shell=Truedengan segala cara.

lunaryorn
sumber
16

Jawaban lain di sini cukup menjelaskan peringatan keamanan yang juga disebutkan dalam subprocessdokumentasi. Tetapi selain itu, overhead memulai shell untuk memulai program yang ingin Anda jalankan seringkali tidak perlu dan tentu saja konyol untuk situasi di mana Anda tidak benar-benar menggunakan fungsionalitas shell. Selain itu, kompleksitas tersembunyi tambahan akan membuat Anda takut, terutama jika Anda tidak terlalu mengenal shell atau layanan yang disediakannya.

Di mana interaksi dengan shell adalah non-trivial, Anda sekarang memerlukan pembaca dan pengelola skrip Python (yang mungkin atau mungkin bukan diri Anda di masa depan) untuk memahami skrip Python dan shell. Ingat moto Python "eksplisit lebih baik daripada implisit"; bahkan ketika kode Python akan menjadi sedikit lebih kompleks daripada skrip shell yang setara (dan seringkali sangat singkat), Anda mungkin lebih baik menghapus shell dan mengganti fungsi dengan konstruksi Python asli. Meminimalkan pekerjaan yang dilakukan dalam proses eksternal dan menjaga kontrol dalam kode Anda sendiri sejauh mungkin seringkali merupakan ide yang baik hanya karena meningkatkan visibilitas dan mengurangi risiko efek samping yang diinginkan atau tidak diinginkan.

Ekspansi wildcard, interpolasi variabel, dan pengalihan semuanya mudah untuk diganti dengan konstruksi Python asli. Sebuah pipeline shell yang kompleks di mana sebagian atau semua tidak dapat ditulis ulang secara wajar dengan Python akan menjadi satu situasi di mana mungkin Anda bisa mempertimbangkan untuk menggunakan shell. Anda harus tetap memastikan bahwa Anda memahami implikasi kinerja dan keamanan.

Dalam kasus sepele, untuk menghindari shell=True, cukup ganti

subprocess.Popen("command -with -options 'like this' and\\ an\\ argument", shell=True)

dengan

subprocess.Popen(['command', '-with','-options', 'like this', 'and an argument'])

Perhatikan bagaimana argumen pertama adalah daftar string untuk diteruskan execvp(), dan bagaimana mengutip string dan karakter karakter shell backslash-escape umumnya tidak diperlukan (atau berguna, atau benar). Mungkin lihat juga Kapan membungkus tanda kutip di sekitar variabel shell?

Sebagai tambahan, Anda sangat sering ingin menghindari Popenjika salah satu pembungkus sederhana dalam subprocesspaket melakukan apa yang Anda inginkan. Jika Anda memiliki Python yang cukup baru, Anda mungkin harus menggunakan subprocess.run.

  • Dengan check=Trueitu akan gagal jika perintah yang Anda jalankan gagal.
  • Dengan stdout=subprocess.PIPEitu akan menangkap output perintah.
  • Agak tidak jelas, dengan universal_newlines=Trueitu akan mendekode output menjadi string Unicode yang tepat (itu hanya bytesdalam sistem pengkodean sebaliknya, pada Python 3).

Jika tidak, untuk banyak tugas, Anda ingin check_outputmendapatkan output dari perintah, sambil memeriksa apakah berhasil, atau check_calljika tidak ada output untuk dikumpulkan.

Saya akan menutup dengan kutipan dari David Korn: "Lebih mudah untuk menulis shell portabel daripada skrip shell portabel." Bahkan subprocess.run('echo "$HOME"', shell=True)tidak portabel untuk Windows.

tripleee
sumber
Saya pikir kutipan itu dari Larry Wall tetapi Google mengatakan sebaliknya.
tripleee
Itu pembicaraan besar - tetapi tidak ada saran teknis untuk penggantian: Inilah saya, di OS-X, mencoba untuk mendapatkan pid dari Mac App yang saya luncurkan melalui 'open': process = subprocess.Popen ('/ usr / bin / pgrep - n '+ app_name, shell = False, stdout = subprocess.PIPE, stderr = subprocess.PIPE) app_pid, err = process.communicate () --- tetapi tidak berfungsi kecuali saya akan menggunakan shell = True. Sekarang apa?
Motti Shneor
Ada banyak pertanyaan tentang cara menghindari shell=True, banyak di antaranya dengan jawaban yang sangat baik. Anda kebetulan memilih yang itulah mengapa .
tripleee
@MottiShneor Terima kasih atas umpan baliknya; menambahkan contoh sederhana
tripleee