Làm cách nào để thoát lệnh gọi os.system ()?


124

Khi sử dụng os.system (), bạn thường phải thoát khỏi tên tệp và các đối số khác được truyền dưới dạng tham số cho lệnh. Tôi có thể làm cái này như thế nào? Tốt hơn là một cái gì đó sẽ hoạt động trên nhiều hệ điều hành / shell nhưng đặc biệt cho bash.

Tôi hiện đang làm những việc sau, nhưng chắc chắn rằng phải có một hàm thư viện cho việc này, hoặc ít nhất là một tùy chọn thanh lịch / mạnh mẽ / hiệu quả hơn:

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)))

Chỉnh sửa: Tôi đã chấp nhận câu trả lời đơn giản là sử dụng dấu ngoặc kép, không biết tại sao tôi không nghĩ đến điều đó; Tôi đoán vì tôi đến từ Windows ở đâu 'và' cư xử hơi khác một chút.

Về bảo mật, tôi hiểu mối quan tâm này, nhưng trong trường hợp này, tôi quan tâm đến một giải pháp nhanh chóng và dễ dàng mà os.system () cung cấp và nguồn của các chuỗi không phải do người dùng tạo hoặc ít nhất là được nhập bởi người dùng đáng tin cậy (tôi).


1
Hãy coi chừng vấn đề bảo mật! Ví dụ: nếu out_filename là foo.txt; rm -rf / Người dùng độc hại có thể thêm nhiều lệnh được trình bao thông dịch trực tiếp.
Steve Gury, 30/08/08

6
Điều này cũng hữu ích mà không cần hệ thống os.system, trong các tình huống mà quy trình con thậm chí không phải là một tùy chọn; ví dụ: tạo các tập lệnh shell.

Một sh_escapehàm lý tưởng sẽ thoát ra khỏi các ;dấu cách và và loại bỏ vấn đề bảo mật bằng cách chỉ cần tạo một tệp có tên giống như vậy foo.txt\;\ rm\ -rf\ /.
Tom

Trong hầu hết mọi trường hợp, bạn nên sử dụng quy trình con, không phải hệ thống os.system. Gọi os.system chỉ là yêu cầu một cuộc tấn công tiêm.
mã allyourcode

Câu trả lời:


85

Đây là những gì tôi sử dụng:

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

Trình bao sẽ luôn chấp nhận một tên tệp được trích dẫn và loại bỏ các dấu ngoặc kép xung quanh trước khi chuyển nó đến chương trình được đề cập. Đáng chú ý, điều này tránh được các vấn đề với tên tệp chứa khoảng trắng hoặc bất kỳ loại siêu ký tự shell khó chịu nào khác.

Cập nhật : Nếu bạn đang sử dụng Python 3.3 trở lên, hãy sử dụng shlex.quote thay vì tự sử dụng .


7
@pixelbeat: đó chính xác là lý do tại sao anh ấy đóng các dấu ngoặc kép của mình, thêm một câu trích dẫn đơn theo nghĩa đen thoát ra và sau đó mở lại các dấu ngoặc kép của mình một lần nữa.
lhunath

4
Mặc dù đây hầu như không phải là trách nhiệm của hàm shellquote, nhưng có thể thú vị khi lưu ý rằng điều này vẫn sẽ không thành công nếu một dấu gạch chéo ngược chưa được trích dẫn xuất hiện ngay trước giá trị trả về của hàm này. Tinh thần: đảm bảo bạn sử dụng mã này trong mã mà bạn có thể tin tưởng là an toàn - (chẳng hạn như một phần của các lệnh được mã hóa cứng) - không nối nó vào đầu vào của người dùng chưa được trích dẫn khác.
lhunath

10
Lưu ý rằng trừ khi bạn thực sự cần các tính năng của shell, bạn có thể nên sử dụng gợi ý của Jamie để thay thế.
lhunath

6
Một cái gì đó tương tự như thế này hiện đã chính thức có sẵn dưới dạng shlex.quote .
Janus Troelsen

3
Hàm được cung cấp trong câu trả lời này thực hiện công việc trích dẫn shell tốt hơn shlexhoặc pipes. Những mô-đun python sai lầm cho rằng nhân vật đặc biệt là điều duy nhất mà cần phải được trích dẫn, có nghĩa là shell từ khóa (như time, casehoặc while) sẽ được phân tích khi hành vi đó không được mong đợi. Vì lý do đó, tôi khuyên bạn nên sử dụng quy trình trích dẫn đơn trong câu trả lời này, bởi vì nó không cố gắng trở nên "thông minh" nên không có những trường hợp ngớ ngẩn đó.
user3035772

157

shlex.quote() làm những gì bạn muốn kể từ python 3.

(Sử dụng pipes.quoteđể hỗ trợ cả python 2 và python 3)


Còn commands.mkargnữa. Nó cũng thêm một khoảng trống ở đầu (bên ngoài dấu ngoặc kép) có thể mong muốn hoặc không. Điều thú vị là cách triển khai của chúng khá khác nhau và cũng phức tạp hơn nhiều so với câu trả lời của Greg Hewgill.
Laurence Gonsalves

3
Vì một số lý do, pipes.quotekhông được đề cập trong tài liệu thư viện tiêu chuẩn cho mô-đun ống
Ngày

1
Cả hai đều không có giấy tờ; command.mkargkhông được dùng nữa và bị loại bỏ trong 3.x, trong khi pipe.quote vẫn còn.
Beni Cherniavsky-Paskin

9
Sửa chữa: được ghi lại chính thức như shlex.quote()trong 3.3, pipes.quote()được giữ lại để tương thích. [ bug.python.org/issue9723]
Beni Cherniavsky-Paskin

7
pipe KHÔNG hoạt động trên Windows - thêm dấu nháy đơn xen kẽ dấu nháy kép.
Nux

58

Có lẽ bạn có một lý do cụ thể để sử dụng os.system(). Nhưng nếu không, bạn có thể nên sử dụng subprocessmô-đun . Bạn có thể chỉ định các đường ống trực tiếp và tránh sử dụng vỏ.

Sau đây là từ 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(đặc biệt là với check_callvv) thường vượt trội hơn đáng kể, nhưng có một số trường hợp thoát shell vẫn hữu ích. Vấn đề chính mà tôi đang gặp phải là khi tôi phải gọi các lệnh từ xa ssh.
Craig Ringer

@CraigRinger, vâng, rất tiếc là điều đã đưa tôi đến đây. : PI ước ssh có thứ gì đó để giúp đỡ ở đây.
Jürgen A. Erhard

@ JürgenA.Erhard Có vẻ kỳ lạ là nó không có tùy chọn --execvp-remote (hoặc hoạt động theo cách đó theo mặc định). Làm mọi thứ qua lớp vỏ có vẻ vụng về và rủi ro. OTOH, ssh đầy rẫy những điều kỳ quặc, thường là những việc được thực hiện theo quan điểm hạn hẹp về "bảo mật" khiến mọi người phải đưa ra những cách giải quyết không an toàn hơn.
Craig Ringer

10

Có lẽ subprocess.list2cmdlinelà một shot tốt hơn?


Điều đó trông khá tốt. Điều thú vị là nó không được ghi lại ... ( ít nhất là trong docs.python.org/library/subprocess.html )
Tom

4
Nó không đúng cách thoát \: subprocess.list2cmdline(["'",'',"\\",'"'])cho' "" \ \"
Tino

Nó không thoát khỏi những biểu tượng mở rộng vỏ
grep

Subprocess.list2cmdline () có phải chỉ dành cho Windows không?
JS.

@JS Có, list2cmdlinetuân theo cú pháp cmd.exe của Windows ( xem hàm docstring trong mã nguồn Python ). shlex.quotetuân theo cú pháp shell của Unix, tuy nhiên nó thường không cần thiết vì Unix hỗ trợ tốt cho việc truyền trực tiếp các đối số. Windows khá nhiều yêu cầu bạn chuyển một chuỗi duy nhất với tất cả các đối số của bạn (do đó cần phải thoát đúng cách).
eestrada

7

Lưu ý rằng pipe.quote thực sự bị hỏng trong Python 2.5 và Python 3.1 và không an toàn để sử dụng - Nó không xử lý các đối số có độ dài bằng không.

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

Xem vấn đề Python 7476 ; nó đã được sửa trong Python 2.6 và 3.2 và mới hơn.


4
Bạn đang sử dụng phiên bản Python nào? Phiên bản 2.6 dường như để tạo đầu ra chính xác: mycommand arg1 '' arg3 (Đó là hai đơn dấu ngoặc kép với nhau, mặc dù font trên Stack Overflow làm cho rằng khó có thể nói!)
Brandon Rhodes

4

Để ý : Đây là câu trả lời cho Python 2.7.x.

Theo nguồn , pipes.quote()là một cách để " Trích dẫn đáng tin cậy một chuỗi như một đối số duy nhất cho / bin / sh ". (Mặc dù nó không được chấp nhận kể từ phiên bản 2.7 và cuối cùng đã được công khai trong Python 3.3 dưới dạngshlex.quote() hàm.)

Mặt khác , subprocess.list2cmdline()là một cách để " Dịch một chuỗi các đối số thành một chuỗi dòng lệnh, sử dụng các quy tắc tương tự như thời gian chạy MS C ".

Đây là cách độc lập của nền tảng để trích dẫn các chuỗi cho các dòng lệnh.

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)

Sử dụng:

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

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

3

Tôi tin rằng os.system chỉ gọi bất kỳ lệnh shell nào được cấu hình cho người dùng, vì vậy tôi không nghĩ rằng bạn có thể làm điều đó theo cách độc lập với nền tảng. Lệnh shell của tôi có thể là bất cứ thứ gì từ bash, emacs, ruby, hoặc thậm chí là quake3. Một số chương trình này không mong đợi loại đối số mà bạn đang chuyển cho chúng và ngay cả khi chúng đã làm vậy thì không có gì đảm bảo rằng chúng sẽ thoát theo cùng một cách.


2
Không phải vô lý khi mong đợi một shell tuân thủ hầu hết hoặc hoàn toàn POSIX (ít nhất là ở mọi nơi, trừ Windows và bạn biết mình có "shell" nào sau đó). os.system không sử dụng $ SHELL, ít nhất là không ở đây.

2

Chức năng tôi sử dụng là:

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

nghĩa là: Tôi luôn đặt đối số trong dấu ngoặc kép, sau đó dấu gạch chéo ngược-trích dẫn các ký tự đặc biệt duy nhất bên trong dấu ngoặc kép.


Lưu ý rằng bạn nên sử dụng '\\ "', '\\ $' và '\`', nếu không thoát không xảy ra.
JanKanis

1
Ngoài ra, có vấn đề với việc sử dụng dấu ngoặc kép trong một số ngôn ngữ (lạ) ; cách sử dụng bản sửa lỗi được đề xuất pipes.quotemà @JohnWiseman đã chỉ ra cũng bị hỏng. Vì vậy, câu trả lời của Greg Hewgill là câu trả lời nên sử dụng. (Nó cũng là một trong những vỏ sử dụng nội bộ cho các trường hợp thông thường.)
mirabilos

-3

Nếu bạn sử dụng lệnh hệ thống, tôi sẽ thử và đưa vào danh sách trắng những gì đi vào lệnh gọi os.system () .. Ví dụ: ..

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

Mô-đun quy trình con là một lựa chọn tốt hơn và tôi khuyên bạn nên cố gắng tránh sử dụng bất kỳ thứ gì như os.system / subprocess nếu có thể.


-3

Câu trả lời thực sự là: Không sử dụng os.system()ngay từ đầu. Sử dụng subprocess.callthay thế và cung cấp các đối số không thoát.


6
Câu hỏi chứa một ví dụ trong đó quy trình con thất bại. Nếu bạn có thể sử dụng quy trình con, bạn nên làm như vậy. Nhưng nếu bạn không thể ... quy trình con không phải là giải pháp cho mọi thứ . Ồ, câu trả lời của bạn hoàn toàn không trả lời câu hỏi.
Jürgen A. Erhard

@ JürgenA.Erhard không phải ví dụ của OP không thành công vì anh ấy muốn sử dụng các ống shell? Bạn luôn nên sử dụng quy trình con nó không sử dụng trình bao. Đây là một ví dụ hơi vụng về , nhưng bạn có thể thực hiện các đường ống trong các quy trình con gốc, có một vài gói pypi cố gắng làm cho việc này dễ dàng hơn. Tôi có xu hướng chỉ thực hiện xử lý hậu kỳ mà tôi cần trong python càng nhiều càng tốt, Bạn luôn có thể tạo bộ đệm StringIO của riêng mình và kiểm soát mọi thứ khá hoàn toàn bằng các quy trình con.
ThorSummoner
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.