Bagaimana cara keluar dari panggilan os.system ()?


124

Saat menggunakan os.system (), sering kali perlu untuk keluar dari nama file dan argumen lain yang diteruskan sebagai parameter ke perintah. Bagaimana saya bisa melakukan ini? Lebih disukai sesuatu yang akan bekerja pada beberapa sistem operasi / shell tetapi khususnya untuk bash.

Saat ini saya melakukan hal berikut, tetapi saya yakin harus ada fungsi pustaka untuk ini, atau setidaknya opsi yang lebih elegan / kuat / efisien:

def sh_escape(s):
   return s.replace("(","\\(").replace(")","\\)").replace(" ","\\ ")

os.system("cat %s | grep something | sort > %s" 
          % (sh_escape(in_filename), 
             sh_escape(out_filename)))

Sunting: Saya telah menerima jawaban sederhana menggunakan tanda kutip, tidak tahu mengapa saya tidak memikirkannya; Saya kira karena saya berasal dari Windows di mana 'dan "berperilaku sedikit berbeda.

Mengenai keamanan, saya memahami kekhawatirannya, tetapi, dalam kasus ini, saya tertarik dengan solusi cepat dan mudah yang disediakan os.system (), dan sumber string bukan buatan pengguna atau setidaknya dimasukkan oleh pengguna tepercaya (saya).


1
Waspadai masalah keamanan! Misalnya jika out_filename adalah foo.txt; rm -rf / Pengguna jahat dapat menambahkan lebih banyak perintah yang langsung diinterpretasikan oleh shell.
Steve Gury

6
Ini juga berguna tanpa os.system, dalam situasi di mana subproses bahkan bukan pilihan; misalnya membuat skrip shell.

sh_escapeFungsi yang ideal akan keluar dari ;spasi dan dan menghilangkan masalah keamanan hanya dengan membuat file bernama seperti foo.txt\;\ rm\ -rf\ /.
Tom

Di hampir semua kasus, Anda harus menggunakan subproses, bukan os.system. Memanggil os.system hanya meminta serangan injeksi.
allyourcode

Jawaban:


85

Ini yang saya gunakan:

def shellquote(s):
    return "'" + s.replace("'", "'\\''") + "'"

Shell akan selalu menerima nama file yang dikutip dan menghapus kutipan di sekitarnya sebelum meneruskannya ke program yang dimaksud. Khususnya, ini untuk menghindari masalah dengan nama file yang mengandung spasi atau jenis karakter metakarakter cangkang jahat lainnya.

Pembaruan : Jika Anda menggunakan Python 3.3 atau yang lebih baru, gunakan shlex.quote alih-alih menggulung milik Anda sendiri.


7
@pixelbeat: itulah mengapa dia menutup kutipan tunggal, menambahkan kutipan tunggal literal yang lolos, dan kemudian membuka kembali kutipan tunggalnya.
lhunath

4
Meskipun hal ini bukanlah tanggung jawab fungsi shellquote, mungkin menarik untuk dicatat bahwa ini masih akan gagal jika garis miring terbalik tanpa tanda kutip muncul tepat sebelum nilai kembalian dari fungsi ini. Moral: pastikan Anda menggunakan ini dalam kode yang dapat Anda percayai sebagai aman - (seperti bagian dari perintah hardcode) - jangan menambahkannya ke input pengguna tanpa tanda kutip lainnya.
lhunath

10
Perhatikan bahwa kecuali Anda benar-benar membutuhkan fitur shell, Anda mungkin sebaiknya menggunakan saran Jamie.
lhunath

6
Sesuatu yang mirip dengan ini sekarang secara resmi tersedia sebagai shlex.quote .
Janus Troelsen

3
Fungsi yang diberikan dalam jawaban ini melakukan pekerjaan kutipan shell dengan lebih baik daripada shlexatau pipes. Modul python tersebut secara keliru mengasumsikan bahwa karakter khusus adalah satu-satunya hal yang perlu dikutip, yang berarti bahwa kata kunci shell (seperti time, caseatau while) akan diurai ketika perilaku itu tidak diharapkan. Untuk alasan itu saya akan merekomendasikan menggunakan rutinitas kutipan tunggal dalam jawaban ini, karena tidak mencoba menjadi "pintar" sehingga tidak memiliki kasus tepi yang konyol.
pengguna3035772

157

shlex.quote() melakukan apa yang Anda inginkan sejak python 3.

(Gunakan pipes.quoteuntuk mendukung python 2 dan python 3)


Ada juga commands.mkarg. Ini juga menambahkan spasi di depan (di luar tanda kutip) yang mungkin diinginkan atau tidak diinginkan. Sangat menarik bagaimana penerapannya sangat berbeda satu sama lain, dan juga jauh lebih rumit daripada jawaban Greg Hewgill.
Laurence Gonsalves

3
Untuk beberapa alasan, pipes.quotetidak disebutkan oleh dokumentasi perpustakaan standar untuk modul pipa
Hari

1
Keduanya tidak berdokumen; command.mkargtidak digunakan lagi dan dihapus di 3.x, sementara pipe.quote tetap ada.
Beni Cherniavsky-Paskin

9
Koreksi: secara resmi didokumentasikan seperti shlex.quote()pada 3.3, pipes.quote()dipertahankan untuk kompatibilitas. [ bugs.python.org/issue9723]
Beni Cherniavsky-Paskin

7
pipe TIDAK berfungsi di Windows - menambahkan tanda kutip tunggal dari tanda kutip ganda.
Nux

58

Mungkin Anda memiliki alasan khusus untuk menggunakan os.system(). Tetapi jika tidak, Anda mungkin harus menggunakan subprocessmodul . Anda dapat menentukan pipa secara langsung dan menghindari penggunaan cangkang.

Berikut ini adalah dari PEP324 :

Replacing shell pipe line
-------------------------

output=`dmesg | grep hda`
==>
p1 = Popen(["dmesg"], stdout=PIPE)
p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
output = p2.communicate()[0]

6
subprocess(terutama dengan check_calldll) seringkali jauh lebih unggul, tetapi ada beberapa kasus di mana pelolosan shell masih berguna. Yang utama yang saya hadapi adalah ketika saya harus menjalankan perintah ssh remote.
Craig Ringer

@CraigRinger, yup, ssh remoting lah yang membawaku ke sini. : Saya berharap ssh punya sesuatu untuk membantu di sini.
Jürgen A. Erhard

@ JürgenA.Erhard Tampaknya aneh bahwa ia tidak memiliki opsi --execvp-remote (atau bekerja seperti itu secara default). Melakukan segala sesuatu melalui cangkang tampaknya kikuk dan berisiko. OTOH, ssh penuh dengan kebiasaan aneh, sering kali hal-hal dilakukan dalam pandangan sempit tentang "keamanan" yang menyebabkan orang menemukan solusi yang jauh lebih tidak aman.
Craig Ringer

10

Mungkin subprocess.list2cmdlinetembakan yang lebih baik?


Kelihatannya bagus. Menarik itu tidak didokumentasikan ... ( setidaknya di docs.python.org/library/subprocess.html )
Tom

4
Itu tidak benar-benar melarikan diri \: subprocess.list2cmdline(["'",'',"\\",'"'])memberi' "" \ \"
Tino

Itu tidak luput dari simbol ekspansi shell
grep

Apakah subprocess.list2cmdline () hanya ditujukan untuk Windows?
JS.

@JS Ya, list2cmdlinesesuai dengan sintaks cmd.exe Windows ( lihat fungsi docstring di kode sumber Python ). shlex.quotesesuai dengan sintaks shell bourne Unix, namun biasanya tidak diperlukan karena Unix memiliki dukungan yang baik untuk mengirimkan argumen secara langsung. Windows cukup banyak mengharuskan Anda melewatkan satu string dengan semua argumen Anda (dengan demikian kebutuhan untuk melarikan diri yang tepat).
eestrada

7

Perhatikan bahwa pipe.quote sebenarnya rusak di Python 2.5 dan Python 3.1 dan tidak aman digunakan - Ini tidak menangani argumen panjang-nol.

>>> from pipes import quote
>>> args = ['arg1', '', 'arg3']
>>> print 'mycommand %s' % (' '.join(quote(arg) for arg in args))
mycommand arg1  arg3

Lihat masalah Python 7476 ; itu telah diperbaiki dengan Python 2.6 dan 3.2 dan yang lebih baru.


4
Versi Python apa yang Anda gunakan? Versi 2.6 tampaknya menghasilkan keluaran yang benar: mycommand arg1 '' arg3 (Itu adalah dua tanda kutip tunggal, meskipun font pada Stack Overflow membuatnya sulit untuk diceritakan!)
Brandon Rhodes

4

Perhatikan : Ini adalah jawaban untuk Python 2.7.x.

Menurut sumbernya , pipes.quote()adalah cara untuk " Mengutip string sebagai argumen tunggal untuk / bin / sh ". (Meskipun tidak digunakan lagi sejak versi 2.7 dan akhirnya diekspos secara publik dengan Python 3.3 sebagai shlex.quote()fungsinya.)

Di sisi lain , subprocess.list2cmdline()adalah cara untuk " Menerjemahkan urutan argumen menjadi string baris perintah, menggunakan aturan yang sama seperti runtime MS C ".

Inilah kami, platform cara independen mengutip string untuk baris perintah.

import sys
mswindows = (sys.platform == "win32")

if mswindows:
    from subprocess import list2cmdline
    quote_args = list2cmdline
else:
    # POSIX
    from pipes import quote

    def quote_args(seq):
        return ' '.join(quote(arg) for arg in seq)

Pemakaian:

# Quote a single argument
print quote_args(['my argument'])

# Quote multiple arguments
my_args = ['This', 'is', 'my arguments']
print quote_args(my_args)

3

Saya percaya bahwa os.system hanya memanggil shell perintah apa pun yang dikonfigurasi untuk pengguna, jadi saya rasa Anda tidak dapat melakukannya dengan cara platform independen. Shell perintah saya bisa apa saja dari bash, emacs, ruby, atau bahkan quake3. Beberapa dari program ini tidak mengharapkan jenis argumen yang Anda berikan kepada mereka dan bahkan jika mereka melakukannya, tidak ada jaminan mereka melakukan pelarian dengan cara yang sama.


2
Tidaklah tidak masuk akal untuk mengharapkan shell yang sebagian besar atau sepenuhnya sesuai dengan POSIX (setidaknya di semua tempat kecuali dengan Windows, dan Anda tahu "shell" apa yang Anda miliki). os.system tidak menggunakan $ SHELL, setidaknya tidak di sini.

2

Fungsi yang saya gunakan adalah:

def quote_argument(argument):
    return '"%s"' % (
        argument
        .replace('\\', '\\\\')
        .replace('"', '\\"')
        .replace('$', '\\$')
        .replace('`', '\\`')
    )

yaitu: Saya selalu menyertakan argumen dalam tanda kutip ganda, dan kemudian tanda kutip mundur satu-satunya karakter khusus di dalam tanda kutip ganda.


Perhatikan bahwa Anda harus menggunakan '\\ "', '\\ $' dan '\`', jika tidak, pelolosan tidak akan terjadi.
JanKanis

1
Selain itu, ada masalah dengan penggunaan tanda kutip ganda di beberapa lokal (aneh) ; penggunaan perbaikan yang disarankan pipes.quoteyang ditunjukkan @JohnWiseman juga rusak. Jadi, jawaban Greg Hewgill adalah yang akan digunakan. (Ini juga yang digunakan shell secara internal untuk kasus biasa.)
mirabilos

-3

Jika Anda menggunakan perintah sistem, saya akan mencoba dan memasukkan ke daftar putih apa yang masuk ke panggilan os.system () .. Misalnya ..

clean_user_input re.sub("[^a-zA-Z]", "", user_input)
os.system("ls %s" % (clean_user_input))

Modul subproses adalah pilihan yang lebih baik, dan saya akan merekomendasikan untuk mencoba menghindari penggunaan apa pun seperti os.system / subprocess sedapat mungkin.


-3

Jawaban sebenarnya adalah: Jangan gunakan os.system()sejak awal. Gunakan subprocess.callsebagai gantinya dan berikan argumen yang tidak lolos.


6
Pertanyaan tersebut berisi contoh di mana subproses baru saja gagal. Jika Anda dapat menggunakan subproses, Anda harus, yakin. Tetapi jika Anda tidak bisa ... subproses bukanlah solusi untuk semuanya . Oh, dan jawaban Anda tidak menjawab pertanyaan itu sama sekali.
Jürgen A. Erhard

@ JürgenA.Erhard bukankah contoh OP gagal karena dia ingin menggunakan pipa shell? Anda harus selalu menggunakan subproses karena tidak menggunakan shell. Ini adalah contoh yang agak kikuk , tetapi Anda dapat melakukan pipe di subproses asli, ada beberapa paket pypi yang mencoba membuatnya lebih mudah. Saya cenderung hanya melakukan pasca-pemrosesan yang saya butuhkan dengan python sebanyak mungkin, Anda selalu dapat membuat buffer StringIO Anda sendiri dan mengontrol semuanya dengan cukup lengkap dengan subproses.
ThorSummoner
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.