Chạy các lệnh Bash trong Python


299

Trên máy cục bộ của tôi, tôi chạy một kịch bản python có chứa dòng này

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
os.system(bashCommand)

Điều này hoạt động tốt.

Sau đó, tôi chạy cùng một mã trên một máy chủ và tôi nhận được thông báo lỗi sau

'import site' failed; use -v for traceback
Traceback (most recent call last):
File "/usr/bin/cwm", line 48, in <module>
from swap import  diag
ImportError: No module named swap

Vì vậy, những gì tôi đã làm sau đó là tôi chèn một print bashCommandbản in cho tôi hơn lệnh trong thiết bị đầu cuối trước khi nó chạy nó os.system().

Tất nhiên, tôi lại nhận được lỗi (do os.system(bashCommand)) gây ra nhưng trước lỗi đó, nó sẽ in lệnh trong thiết bị đầu cuối. Sau đó, tôi chỉ sao chép đầu ra đó và thực hiện sao chép vào thiết bị đầu cuối và nhấn enter và nó hoạt động ...

Có ai có manh mối những gì đang xảy ra?


2
Dường như có một sự khác biệt trong môi trường tùy thuộc vào cách bạn chạy cwm. Có lẽ bạn có một số cấu hình trong .bashrcđó thiết lập môi trường để sử dụng bash tương tác?
Sven Marnach

Bạn đã thử chạy lệnh từ dòng lệnh khi đăng nhập trên máy chủ chưa? Bài viết của bạn chỉ nói rằng bạn "dán [nó] vào thiết bị đầu cuối".
Sven Marnach

@Sven: vâng, ý tôi là tôi đã chạy lệnh trực tiếp trong thiết bị đầu cuối của máy chủ
mkn

Dường như có một sự khác biệt trong PYTHONPATH tùy thuộc vào cách bạn chạy cwm. Hoặc có thể có một sự khác biệt trong PATH và phiên bản khác nhau cwmđược gọi. Hoặc các phiên bản khác nhau của Python. Thật sự rất khó để tìm ra điều này mà không cần truy cập vào máy ...
Sven Marnach

Câu trả lời:


314

Đừng sử dụng os.system. Nó đã không được ủng hộ trong quá trình con . Từ các tài liệu : "Mô-đun này dự định thay thế một số mô-đun và chức năng cũ hơn : os.system, os.spawn".

Giống như trong trường hợp của bạn:

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
import subprocess
process = subprocess.Popen(bashCommand.split(), stdout=subprocess.PIPE)
output, error = process.communicate()

8
Điều này đã không làm những gì tôi muốn khi tôi cần thực hiện cd 'path\to\somewhere'theo sau bởi một lệnh bash khác cần được chạy ở đâu đó. @ user225312
AWrightIV

36
@AWrightIV Nếu bạn cần chạy quy trình con của mình trong một thư mục làm việc cụ thể, bạn có thể sử dụng cwdđối số để Popen:subprocess.Popen(..., cwd='path\to\somewhere')
không thấm nước

7
Đối với lệnh của tôi, tôi cần shell = Đúng như ở đây; stackoverflow.com/questions/18962785/
Mạnh

4
Tốt hơn nên sử dụng shlex.split () thay vì string.split () trong trường hợp này
Alexey Sviridov

4
... ( stdout=fileChuyển hướng đầu ra thành một tệp trong trường hợp này. Nó thực hiện > file). Sẽ là sai khi truyền ..., '>', 'file']lệnh cuối cùng mong đợi chuyển hướng (nó sẽ không hoạt động nếu không có shell và nếu bạn sử dụng shell, bạn nên truyền lệnh dưới dạng chuỗi)
jfs

186

Để phần nào mở rộng các câu trả lời trước đây ở đây, có một số chi tiết thường bị bỏ qua.

  • Thích subprocess.run()hơn subprocess.check_call()và bạn bè trên subprocess.call()trên subprocess.Popen()trên os.system()trênos.popen()
  • Hiểu và có thể sử dụng text=True, aka universal_newlines=True.
  • Hiểu ý nghĩa của shell=Truehoặc shell=Falsevà cách nó thay đổi trích dẫn và sự sẵn có của tiện ích vỏ.
  • Hiểu sự khác biệt giữa shvà Bash
  • Hiểu cách một quy trình con tách biệt với cha mẹ của nó và thường không thể thay đổi cha mẹ.
  • Tránh chạy trình thông dịch Python như một quy trình con của Python.

Những chủ đề này được đề cập trong một số chi tiết dưới đây.

Thích subprocess.run()hoặcsubprocess.check_call()

Các subprocess.Popen()chức năng là một Workhorse ở mức độ thấp nhưng nó là khó khăn để sử dụng một cách chính xác và bạn kết thúc sao chép / dán nhiều dòng mã ... mà thuận tiện đã tồn tại trong thư viện chuẩn như một tập hợp các cấp cao hơn wrapper chức năng cho các mục đích khác nhau, được trình bày chi tiết hơn trong phần sau.

Đây là một đoạn trong tài liệu :

Cách tiếp cận được đề xuất để gọi các quy trình con là sử dụng run()hàm cho tất cả các trường hợp sử dụng mà nó có thể xử lý. Đối với các trường hợp sử dụng nâng cao hơn, Popengiao diện bên dưới có thể được sử dụng trực tiếp.

Thật không may, tính khả dụng của các hàm bao bọc này khác nhau giữa các phiên bản Python.

  • subprocess.run()đã được giới thiệu chính thức trong Python 3.5. Nó có nghĩa là để thay thế tất cả những điều sau đây.
  • subprocess.check_output()đã được giới thiệu trong Python 2.7 / 3.1. Về cơ bản là tương đương vớisubprocess.run(..., check=True, stdout=subprocess.PIPE).stdout
  • subprocess.check_call()đã được giới thiệu trong Python 2.5. Về cơ bản là tương đương vớisubprocess.run(..., check=True)
  • subprocess.call()đã được giới thiệu trong Python 2.4 trong subprocessmô-đun gốc ( PEP-324 ). Về cơ bản là tương đương vớisubprocess.run(...).returncode

API cấp cao so với subprocess.Popen()

Các cấu trúc lại và mở rộng subprocess.run()là hợp lý và linh hoạt hơn so với các chức năng cũ hơn mà nó thay thế. Nó trả về một CompletedProcessđối tượng có các phương thức khác nhau cho phép bạn truy xuất trạng thái thoát, đầu ra tiêu chuẩn và một vài kết quả và chỉ báo trạng thái khác từ quy trình con đã hoàn thành.

subprocess.run()là cách để đi nếu bạn chỉ cần một chương trình để chạy và trả lại quyền điều khiển cho Python. Đối với các kịch bản có liên quan nhiều hơn (các quy trình nền, có lẽ với I / O tương tác với chương trình mẹ Python), bạn vẫn cần phải sử dụng subprocess.Popen()và tự mình chăm sóc tất cả các hệ thống ống nước. Điều này đòi hỏi một sự hiểu biết khá phức tạp về tất cả các bộ phận chuyển động và không nên được thực hiện nhẹ. PopenĐối tượng đơn giản hơn đại diện cho quá trình (có thể vẫn đang chạy) cần được quản lý từ mã của bạn trong phần còn lại của vòng đời của quy trình con.

Có lẽ nên nhấn mạnh rằng chỉ subprocess.Popen()đơn thuần là tạo ra một quá trình. Nếu bạn để nó ở đó, bạn có một quy trình con chạy đồng thời cùng với Python, vì vậy quá trình "nền". Nếu nó không cần thực hiện đầu vào hoặc đầu ra hoặc phối hợp với bạn, nó có thể thực hiện công việc hữu ích song song với chương trình Python của bạn.

Tránh os.system()os.popen()

Kể từ thời gian vĩnh cửu (tốt, kể từ Python 2.5), ostài liệu mô-đun đã chứa khuyến nghị thích subprocesshơn os.system():

Các subprocessmô-đun cung cấp cơ sở vật chất mạnh mẽ hơn để đẻ trứng quy trình mới và lấy kết quả của họ; sử dụng mô-đun đó là tốt hơn để sử dụng chức năng này.

Vấn đề system()là nó rõ ràng phụ thuộc vào hệ thống và không cung cấp cách để tương tác với quy trình con. Nó chỉ đơn giản là chạy, với đầu ra tiêu chuẩn và lỗi tiêu chuẩn nằm ngoài tầm với của Python. Thông tin duy nhất Python nhận được là trạng thái thoát của lệnh (zero có nghĩa là thành công, mặc dù ý nghĩa của các giá trị khác không cũng phụ thuộc vào hệ thống).

PEP-324 (đã được đề cập ở trên) chứa một lý do chi tiết hơn về lý do tại sao os.systemcó vấn đề và cách subprocesscố gắng giải quyết các vấn đề đó.

os.popen()đã từng được khuyến khích mạnh mẽ hơn nữa :

Không dùng nữa kể từ phiên bản 2.6: Chức năng này đã lỗi thời. Sử dụng subprocessmô-đun.

Tuy nhiên, vì đôi khi trong Python 3, nó đã được thực hiện lại chỉ đơn giản là sử dụng subprocessvà chuyển hướng đến subprocess.Popen()tài liệu để biết chi tiết.

Hiểu và thường sử dụng check=True

Bạn cũng sẽ nhận thấy rằng subprocess.call()có nhiều hạn chế giống như os.system(). Trong sử dụng thường xuyên, bạn thường nên kiểm tra xem quy trình đã kết thúc thành công chưa, subprocess.check_call()subprocess.check_output()thực hiện (trong đó quy trình sau cũng trả về đầu ra tiêu chuẩn của quy trình con đã hoàn thành). Tương tự, bạn thường nên sử dụng check=Truevới subprocess.run()trừ khi bạn đặc biệt cần cho phép quy trình con trả về trạng thái lỗi.

Trong thực tế, với check=Truehoặc subprocess.check_*, Python sẽ đưa ra một CalledProcessErrorngoại lệ nếu quy trình con trả về trạng thái thoát khác.

Một lỗi phổ biến subprocess.run()là bỏ sót check=Truevà ngạc nhiên khi mã nguồn bị lỗi nếu quá trình con thất bại.

Mặt khác, một vấn đề phổ biến check_call()check_output()là người dùng sử dụng các chức năng này một cách mù quáng đã rất ngạc nhiên khi ngoại lệ được nêu ra, ví dụ như khi grepkhông tìm thấy kết quả khớp. ( grepDù sao thì bạn cũng nên thay thế bằng mã Python nguyên gốc, như được nêu dưới đây.)

Tất cả mọi thứ được tính, bạn cần hiểu cách các lệnh shell trả về mã thoát và trong những điều kiện nào chúng sẽ trả về mã thoát không (lỗi) và đưa ra quyết định có ý thức về cách xử lý chính xác.

Hiểu và có thể sử dụng text=Trueakauniversal_newlines=True

Vì Python 3, các chuỗi bên trong Python là các chuỗi Unicode. Nhưng không có gì đảm bảo rằng một quy trình con tạo ra đầu ra Unicode hoặc các chuỗi.

(Nếu sự khác biệt không rõ ràng ngay lập tức, Unicode thực dụng của Ned Batchelder được khuyến nghị, nếu không hoàn toàn bắt buộc, hãy đọc. Có một bài thuyết trình video dài 36 phút đằng sau liên kết nếu bạn thích, mặc dù việc tự đọc trang có thể sẽ mất ít thời gian hơn. )

Sâu xa hơn, Python phải tìm nạp một bytesbộ đệm và giải thích nó bằng cách nào đó. Nếu nó chứa một đốm dữ liệu nhị phân, thì nó không nên được giải mã thành chuỗi Unicode, bởi vì đó là hành vi dễ bị lỗi và gây ra lỗi - chính xác là loại hành vi phiền phức đã đánh đố nhiều tập lệnh Python 2, trước khi có cách phân biệt đúng giữa văn bản được mã hóa và dữ liệu nhị phân.

Với text=True, bạn nói với Python rằng trên thực tế, bạn mong đợi dữ liệu văn bản trở lại trong mã hóa mặc định của hệ thống và nó sẽ được giải mã thành chuỗi Python (Unicode) theo khả năng tốt nhất của Python (thường là UTF-8 trên bất kỳ mức độ vừa phải nào hệ thống ngày tháng, ngoại trừ Windows?)

Nếu đó không phải là những gì bạn yêu cầu trở lại, Python sẽ chỉ cung cấp cho bạn các byteschuỗi trong stdoutstderrchuỗi. Có lẽ tại một số sau đó chỉ cho bạn làm biết rằng họ là chuỗi văn bản sau khi tất cả, và bạn biết mã hóa của họ. Sau đó, bạn có thể giải mã chúng.

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 đã giới thiệu bí danh ngắn hơn và mô tả và dễ hiểu hơn textcho đối số từ khóa mà trước đây được gọi một cách sai lệch universal_newlines.

Hiểu shell=Truevsshell=False

Với shell=Truebạn truyền một chuỗi duy nhất vào vỏ của bạn và vỏ sẽ lấy nó từ đó.

Với việc shell=Falsebạn chuyển một danh sách các đối số cho HĐH, bỏ qua trình bao.

Khi bạn không có vỏ, bạn lưu một quy trình và loại bỏ một lượng phức tạp tiềm ẩn khá lớn, có thể hoặc không thể chứa lỗi hoặc thậm chí là các vấn đề bảo mật.

Mặt khác, khi bạn không có vỏ, bạn không có chuyển hướng, mở rộng ký tự đại diện, kiểm soát công việc và một số lượng lớn các tính năng vỏ khác.

Một lỗi phổ biến là sử dụng shell=Truevà sau đó vẫn chuyển cho Python một danh sách mã thông báo hoặc ngược lại. Điều này xảy ra để làm việc trong một số trường hợp, nhưng thực sự không rõ ràng và có thể phá vỡ theo những cách thú vị.

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

Câu trả lời chung "nhưng nó hoạt động với tôi" không phải là phản bác hữu ích trừ khi bạn hiểu chính xác trong trường hợp nào nó có thể ngừng hoạt động.

Ví dụ tái cấu trúc

Rất thường xuyên, các tính năng của shell có thể được thay thế bằng mã Python gốc. sedThay vào đó, Awk hoặc script đơn giản có lẽ nên được dịch sang Python.

Để minh họa một phần điều này, đây là một ví dụ điển hình nhưng hơi ngớ ngẩn bao gồm nhiều tính năng vỏ.

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

Một số điều cần lưu ý ở đây:

  • Với shell=Falsebạn không cần trích dẫn rằng shell yêu cầu xung quanh chuỗi. Đặt dấu ngoặc kép có lẽ là một lỗi.
  • Nó thường có ý nghĩa để chạy càng ít mã càng tốt trong một quy trình con. Điều này cho phép bạn kiểm soát nhiều hơn việc thực thi từ bên trong mã Python của mình.
  • Phải nói rằng, các đường ống vỏ phức tạp rất tẻ nhạt và đôi khi rất khó để thực hiện lại trong Python.

Mã được cấu trúc lại cũng minh họa mức độ thực sự của vỏ đối với bạn với cú pháp rất ngắn gọn - tốt hơn hoặc xấu hơn. Python nói rõ ràng là tốt hơn so với tiềm ẩn nhưng mã Python khá dài dòng và có lẽ trông phức tạp hơn điều này thực sự là. Mặt khác, nó cung cấp một số điểm mà bạn có thể giành quyền kiểm soát ở giữa một thứ khác, như được minh họa một cách tầm thường bởi sự tăng cường mà chúng ta có thể dễ dàng bao gồm tên máy chủ cùng với đầu ra lệnh shell. (Điều này cũng không phải là thách thức để thực hiện trong vỏ, nhưng với chi phí của một sự chuyển hướng khác và có lẽ là một quá trình khác.)

Cấu trúc vỏ chung

Để đầy đủ, đây là những giải thích ngắn gọn về một số tính năng vỏ này và một số lưu ý về cách chúng có thể được thay thế bằng các phương tiện Python nguyên gốc.

  • Globbing aka mở rộng ký tự đại diện có thể được thay thế bằng glob.glob()hoặc rất thường xuyên bằng các so sánh chuỗi Python đơn giản như thế nào for file in os.listdir('.'): if not file.endswith('.png'): continue. Bash có nhiều phương tiện mở rộng khác như .{png,jpg}mở rộng dấu ngoặc và mở rộng {1..100}dấu ngã ( ~mở rộng sang thư mục chính của bạn và nói chung ~accountlà thư mục chính của người dùng khác)
  • Các biến Shell như $SHELLhoặc $my_exported_varđôi khi có thể chỉ đơn giản được thay thế bằng các biến Python. Biến vỏ xuất khẩu có sẵn như là ví dụ os.environ['SHELL'](ý nghĩa của exportlà làm cho biến sẵn cho subprocesses -. Một biến mà không phải là có sẵn cho subprocesses sẽ rõ ràng là không có sẵn cho Python chạy như một tiến trình con của vỏ, hoặc ngược lại Các env=từ khóa đối số cho subprocesscác phương thức cho phép bạn xác định môi trường của quy trình con là một từ điển, vì vậy đó là một cách để làm cho biến Python hiển thị với quy trình con). Với shell=Falsebạn sẽ cần phải hiểu làm thế nào để loại bỏ bất kỳ trích dẫn; ví dụ, cd "$HOME"tương đương với việc os.chdir(os.environ['HOME'])không có dấu ngoặc kép quanh tên thư mục. (Rất thường xuyêncdDù sao cũng không hữu ích hoặc cần thiết, và nhiều người mới bắt đầu bỏ qua các dấu ngoặc kép xung quanh biến và thoát khỏi nó cho đến một ngày ... )
  • Chuyển hướng cho phép bạn đọc từ một tệp dưới dạng đầu vào tiêu chuẩn của bạn và ghi đầu ra tiêu chuẩn của bạn vào một tệp. grep 'foo' <inputfile >outputfilemở outputfileđể viết và inputfileđể đọc, và chuyển nội dung của nó làm đầu vào tiêu chuẩn grep, đầu ra tiêu chuẩn sau đó rơi vào outputfile. Điều này thường không khó để thay thế bằng mã Python gốc.
  • Đường ống là một hình thức chuyển hướng. echo foo | nlchạy hai quy trình con, trong đó đầu ra tiêu chuẩn echolà đầu vào tiêu chuẩn của nl(ở cấp độ HĐH, trong các hệ thống giống như Unix, đây là một xử lý tệp duy nhất). Nếu bạn không thể thay thế một hoặc cả hai đầu của đường ống bằng mã Python nguyên gốc, có lẽ bạn nên nghĩ đến việc sử dụng shell, đặc biệt là nếu đường ống có nhiều hơn hai hoặc ba quy trình (mặc dù nhìn vào pipesmô-đun trong thư viện chuẩn Python hoặc một số của các đối thủ bên thứ ba hiện đại và linh hoạt hơn).
  • Kiểm soát công việc cho phép bạn làm gián đoạn công việc, chạy chúng trong nền, đưa chúng trở lại nền trước, v.v ... Các tín hiệu Unix cơ bản để dừng và tiếp tục một quá trình tất nhiên cũng có sẵn từ Python. Nhưng các công việc là một sự trừu tượng hóa ở cấp độ cao hơn trong trình bao gồm các nhóm quy trình, v.v. mà bạn phải hiểu nếu bạn muốn làm một cái gì đó như thế này từ Python.
  • Trích dẫn trong shell có khả năng gây nhầm lẫn cho đến khi bạn hiểu rằng mọi thứ về cơ bản là một chuỗi. Vì vậy, ls -l /tương đương với 'ls' '-l' '/'nhưng trích dẫn xung quanh nghĩa đen là hoàn toàn tùy chọn. Các chuỗi không được trích dẫn có chứa các siêu ký tự shell trải qua quá trình mở rộng tham số, mã thông báo khoảng trắng và mở rộng ký tự đại diện; dấu ngoặc kép ngăn chặn mã thông báo khoảng trắng và mở rộng ký tự đại diện nhưng cho phép mở rộng tham số (thay thế biến, thay thế lệnh và xử lý dấu gạch chéo ngược). Điều này là đơn giản trong lý thuyết nhưng có thể gây hoang mang, đặc biệt là khi có một số lớp giải thích (ví dụ một lệnh shell từ xa).

Hiểu sự khác biệt giữa shvà Bash

subprocesschạy các lệnh shell của bạn /bin/shtrừ khi bạn yêu cầu cụ thể khác (tất nhiên ngoại trừ trên Windows, nơi nó sử dụng giá trị của COMSPECbiến). Điều này có nghĩa là các tính năng khác nhau chỉ có Bash như mảng, [[v.v. không có sẵn.

Nếu bạn cần sử dụng cú pháp chỉ Bash, bạn có thể chuyển đường dẫn tới shell dưới dạng executable='/bin/bash'(tất nhiên nếu Bash của bạn được cài đặt ở một nơi khác, bạn cần điều chỉnh đường dẫn).

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 subprocesstách biệt với cha mẹ của nó và không thể thay đổi nó

Một lỗi khá phổ biến là làm một cái gì đó như

subprocess.run('foo=bar', shell=True)
subprocess.run('echo "$foo"', shell=True)  # Doesn't work

mà bên cạnh sự thiếu thanh lịch cũng phản bội sự thiếu hiểu biết cơ bản về phần "phụ" của tên "quy trình con".

Một tiến trình con chạy hoàn toàn tách biệt với Python và khi nó kết thúc, Python không biết nó đã làm gì (ngoài các chỉ số mơ hồ mà nó có thể suy ra từ trạng thái thoát và đầu ra từ tiến trình con). Một đứa trẻ thường không thể thay đổi môi trường của cha mẹ; nó không thể thiết lập một biến, thay đổi thư mục làm việc hoặc, trong rất nhiều từ, giao tiếp với cha mẹ của nó mà không có sự hợp tác từ cha mẹ.

Khắc phục ngay lập tức trong trường hợp cụ thể này là chạy cả hai lệnh trong một quy trình con;

subprocess.run('foo=bar; echo "$foo"', shell=True)

mặc dù rõ ràng trường hợp sử dụng cụ thể này không yêu cầu vỏ. Hãy nhớ rằng, bạn có thể thao túng môi trường của quy trình hiện tại (và cả con của nó) thông qua

os.environ['foo'] = 'bar'

hoặc chuyển một thiết lập môi trường cho một tiến trình con với

subprocess.run('echo "$foo"', shell=True, env={'foo': 'bar'})

(không đề cập đến việc tái cấu trúc rõ ràng subprocess.run(['echo', 'bar']); nhưng echolà một ví dụ nghèo nàn về một cái gì đó để chạy trong một quy trình con ở nơi đầu tiên, tất nhiên).

Đừng chạy Python từ Python

Đây là lời khuyên hơi mơ hồ; chắc chắn có những tình huống có ý nghĩa hoặc thậm chí là một yêu cầu tuyệt đối để chạy trình thông dịch Python như một quy trình con từ tập lệnh Python. Nhưng rất thường xuyên, cách tiếp cận đúng chỉ đơn giản là importmô-đun Python khác vào tập lệnh gọi của bạn và gọi trực tiếp các chức năng của nó.

Nếu tập lệnh Python khác nằm dưới sự kiểm soát của bạn và nó không phải là một mô-đun, hãy xem xét biến nó thành một . (Câu trả lời này đã quá dài rồi nên tôi sẽ không đi sâu vào chi tiết ở đây.)

Nếu bạn cần song song, bạn có thể chạy các hàm Python trong các quy trình con với multiprocessingmô-đun. Ngoài ra còn threadingcó nhiều nhiệm vụ chạy trong một quy trình (nhẹ hơn và cho bạn nhiều quyền kiểm soát hơn, nhưng cũng bị ràng buộc nhiều hơn trong các luồng trong một quy trình được liên kết chặt chẽ và ràng buộc với một GIL duy nhất .)


2
Để biết thêm chi tiết về cách bạn có thể tránh gọi Python là một quy trình con, hãy xem câu trả lời này trong một câu hỏi tương tự tiếp tuyến.
tripleee

4
tôi suy nghĩ rằng tôi phải đăng một câu trả lời mới cho một câu hỏi cơ bản như vậy để chỉ ra cách chạy lệnh từ câu hỏi một cách tự nhiên. Câu trả lời của bạn dài nhưng tôi không thấy ví dụ như vậy. Không liên quan: tránh nuôi cấy hàng hóa. Nếu check_call () hoạt động trong trường hợp của bạn, hãy sử dụng nó. Tôi đã phải sửa một mã sử dụng run()một cách mù quáng. Việc thiếu check=Trueđã gây ra một lỗi có thể tránh được nếu check_call được sử dụng - "check" có trong tên, bạn không thể mất nó. Đây là mặc định chính xác: đừng bỏ qua lỗi một cách im lặng. Tôi đã không đọc thêm.
jfs

1
@jfs Cảm ơn bạn đã phản hồi, trên thực tế tôi đã lên kế hoạch thêm một phần về Bash vs shnhưng bạn đã đánh bại tôi với nó. Tôi đang cố gắng đánh vần các chi tiết cụ thể đủ chi tiết để giúp những người mới bắt đầu mà những cạm bẫy này không rõ ràng để điều đó trở nên hơi dài dòng. Bạn nên khá đủ nếu không; +1
tripleee

stderr/stdout = subprocess.PIPEchi phí hiệu năng cao hơn cài đặt mặc định không?
Chuỗi

1
@Stringers Tôi chưa thử nghiệm, nhưng tôi không hiểu tại sao nên làm vậy. Nếu bạn kết nối các đường ống đó với một cái gì đó thực hiện một số xử lý, thì tất nhiên việc xử lý đó cần phải được xử lý; nhưng nó không xảy ra trong chính đường ống. Mặc định là không chụp stdout hoặc stderr, tức là bất cứ thứ gì được in ra đều nằm ngoài khả năng hiển thị và kiểm soát của Python, giống như với os.system().
tripleee

41

Gọi nó với quy trình con

import subprocess
subprocess.Popen("cwm --rdf test.rdf --ntriples > test.nt")

Lỗi bạn gặp phải dường như là do không có mô-đun trao đổi trên máy chủ, bạn nên cài đặt trao đổi trên máy chủ sau đó chạy lại tập lệnh


3
Các swapmô-đun hiển nhiên là có, bởi vì cách chạy lệnh từ các công trình shell.
Sven Marnach

2
Không phải trên máy chủ, khi anh ta chạy nó trên máy chủ thì có lỗi nhập.
Jakob Bowyer

@mkn: "Sau đó, tôi chỉ sao chép đầu ra đó và sao chép vào thiết bị đầu cuối và nhấn enter và nó hoạt động ..." - Bạn đã thử điều này trên máy chủ hoặc trên máy của bạn chưa?
Sven Marnach

Có phải bạn đang chạy cái này trên một máy tính độc lập nhưng nó không hoạt động khi bạn chạy nó trên máy chủ của bạn? Hoặc bạn có thể chạy nó trên thiết bị đầu cuối máy chủ nhưng không phải chính máy chủ
Jakob Bowyer

1
nó là sai Nếu bạn không sử dụng shell=Truethì bạn nên sử dụng một danh sách để vượt qua nhiều đối số tức là, sử dụng ['a', 'b', 'c']thay vì 'a b c'. Mặc dù một phân chia ngây thơ sẽ không hoạt động do > file(chuyển hướng vỏ) trong lệnh. Thêm chi tiết
jfs

18

Có thể bạn sử dụng chương trình bash, với tham số -c để thực thi các lệnh:

bashCommand = "cwm --rdf test.rdf --ntriples > test.nt"
output = subprocess.check_output(['bash','-c', bashCommand])

2
subprocess.check_output(bashCommand, shell=True)làm điều tương tự Nếu lệnh của bạn là một chuỗi tĩnh, hãy thử tự phân tích nó thành một danh sách và tránh shell=True; mặc dù trong trường hợp này bạn vẫn cần vỏ để chuyển hướng, nếu không bạn sẽ cần cấu trúc lại nó thành Python thuần túy -with open('test.nt', 'w') as dest: output = subprocess.check_output(['cwm' ,'--rdf', 'test.rdf', '--ntriples'], stdout=dest, shell=False)
tripleee

@tripleee lưu ý: /bin/sh(được sử dụng bởi quy trình con) không nhất thiết bash(bạn không thể sử dụng bashism). Mặc dù người ta có thể sử dụng executable='/bin/bashnếu muốn. Đây là một ví dụ mã
jfs

2
nó là câu trả lời đầu tiên mà lệnh nên bắt đầu thành công (được chấp nhận và các câu trả lời phổ biến thứ 2 chỉ là sai Người chưa thành niên không phân minh:. check_output()là vô dụng ở đây (đầu ra luôn luôn trống rỗng do sự > filechuyển hướng; sử dụng check_call(). thay vì
JFS

16

Bạn có thể sử dụng subprocess, nhưng tôi luôn cảm thấy rằng đó không phải là cách làm 'Pythonic'. Vì vậy, tôi đã tạo ra Sultan (phích cắm không biết xấu hổ) giúp dễ dàng chạy các chức năng dòng lệnh.

https://github.com/aeroxis/sultan


3
Làm tốt! Sạch sẽ và trực quan hơn nhiều so với quy trình con.
mjd2

Cảm ơn bạn rất nhiều! Tôi rất vui khi nghe điều đó!
David Daniel

2
Điều này nên thành thật được thông qua vào thư viện tiêu chuẩn.
Joshua Detwiler

1
Có cách nào để nắm bắt đầu ra từ thiết bị đầu cuối bằng cách sử dụng Sultan không?
alvas

Có, bạn có thể @alvas ... Đây là tài liệu về cách thực hiện: sultan.readthedocs.io/en/latest/ mẹo
David Daniel

7

Theo lỗi bạn đang thiếu một gói có tên là trao đổi trên máy chủ. Điều này /usr/bin/cwmđòi hỏi nó. Nếu bạn đang python-swapsử dụng Ubuntu / Debian, hãy cài đặt bằng aptitude.


Nhưng nó hoạt động khi tôi chạy nó trực tiếp trong thiết bị đầu cuối ... vì vậy trao đổi phải ở đó, phải không?
mkn

Có hai lựa chọn. hoặc nó không thể tìm thấy swaphoặc không nên nhập nó ở nơi đầu tiên. bạn có thể làm import swapbằng tay không? nó có hoạt động không
kichik

Tôi không thể. Nếu tôi bắt đầu python bằng cách gõ python trong thiết bị đầu cuối và sau đó tôi nhập trao đổi nhập khẩu thì tôi đã gặp lỗi "ImportError: Không có mô-đun có tên là trao đổi". Điều kỳ lạ vẫn là nó hoạt động khi tôi chạy lệnh cwm trực tiếp trong thiết bị đầu cuối của máy chủ
mkn

Hãy thử in sys.pathở nơi nó hoạt động và nơi không. Sau đó thử tìm thư mục hoán đổi hoặc hoán đổi trong các thư mục được in. Như Sven đã nói, có thể có một vấn đề với những con đường đó, và điều này sẽ giúp bạn tìm ra nó.
kichik

4

Ngoài ra, bạn có thể sử dụng 'os.popen'. Thí dụ:

import os

command = os.popen('ls -al')
print(command.read())
print(command.close())

Đầu ra:

total 16
drwxr-xr-x 2 root root 4096 ago 13 21:53 .
drwxr-xr-x 4 root root 4096 ago 13 01:50 ..
-rw-r--r-- 1 root root 1278 ago 13 21:12 bot.py
-rw-r--r-- 1 root root   77 ago 13 21:53 test.py

None

1
Tài liệu chứa một hộp lớn màu đỏ: " Không dùng nữa kể từ phiên bản 2.6: Chức năng này đã lỗi thời. Sử dụng subprocessmô-đun."
tripleee

1
Công bằng, os.popenkhông còn có cảnh báo này, và chỉ đơn giản là một lớp bọc mỏng xung quanh subprocess.Popen()bây giờ.
tripleee

4

Để chạy lệnh không có shell, hãy truyền lệnh dưới dạng danh sách và thực hiện chuyển hướng trong Python bằng cách sử dụng [subprocess]:

#!/usr/bin/env python
import subprocess

with open('test.nt', 'wb', 0) as file:
    subprocess.check_call("cwm --rdf test.rdf --ntriples".split(),
                          stdout=file)

Lưu ý: không có > test.ntở cuối. stdout=filethực hiện chuyển hướng.


Để chạy lệnh bằng shell trong Python, truyền lệnh dưới dạng chuỗi và bật shell=True:

#!/usr/bin/env python
import subprocess

subprocess.check_call("cwm --rdf test.rdf --ntriples > test.nt",
                      shell=True)

Đây là shell chịu trách nhiệm chuyển hướng đầu ra ( > test.ntnằm trong lệnh).


Để chạy lệnh bash sử dụng bashism, chỉ định rõ ràng bash thực thi, ví dụ, để mô phỏng thay thế quy trình bash :

#!/usr/bin/env python
import subprocess

subprocess.check_call('program <(command) <(another-command)',
                      shell=True, executable='/bin/bash')

Có lẽ đề cập đến điều đó .split()là không đầy đủ khi có các chuỗi được trích dẫn, vv Có một thói quen riêng biệt shlex.split()đối phó với cú pháp shell phức tạp tùy ý.
tripleee

@tripleee các .split()công trình trong trường hợp này. shlex.split()đôi khi có thể hữu ích nhưng nó cũng có thể thất bại trong một số trường hợp. Có rất nhiều điều tuyệt vời có thể được đề cập. Bạn có thể bắt đầu với liên kết đến mô tả thẻ quy trình con được cung cấp ở trên.
jfs

0

Cách thức pythonic để làm điều này là sử dụng subprocess.Popen

subprocess.Popen nhận một danh sách trong đó phần tử đầu tiên là lệnh sẽ được chạy theo sau bởi bất kỳ đối số dòng lệnh nào.

Ví dụ:

import subprocess

args = ['echo', 'Hello!']
subprocess.Popen(args) // same as running `echo Hello!` on cmd line

args2 = ['echo', '-v', '"Hello Again"']
subprocess.Popen(args2) // same as running 'echo -v "Hello Again!"` on cmd line

Không, ví dụ cuối cùng giống như chạy echo -v '"Hello Again!"'với dấu ngoặc đơn xung quanh dấu ngoặc kép.
tripleee

Ngoài ra, để sử dụng chính xác subprocesss.Popen, bạn phải quản lý đối tượng quá trình kết quả (tối thiểu, thực hiện a wait()để ngăn không cho nó biến thành quy trình zombie).
ba
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.