Làm cách nào để chạy lệnh bên ngoài không đồng bộ từ Python?


120

Tôi cần chạy lệnh shell không đồng bộ từ tập lệnh Python. Ý tôi là tôi muốn tập lệnh Python của mình tiếp tục chạy trong khi lệnh bên ngoài tắt và thực hiện bất cứ điều gì nó cần làm.

Tôi đọc bài đăng này:

Gọi lệnh bên ngoài bằng Python

Sau đó, tôi bắt đầu và thực hiện một số thử nghiệm và có vẻ như os.system()sẽ thực hiện công việc với điều kiện tôi sử dụng &ở cuối lệnh để tôi không phải đợi nó quay trở lại. Điều tôi tự hỏi là liệu đây có phải là cách thích hợp để thực hiện một điều như vậy? Tôi đã thử commands.call()nhưng nó sẽ không hoạt động đối với tôi vì nó chặn trên lệnh bên ngoài.

Xin vui lòng cho tôi biết nếu sử dụng os.system()cho điều này được khuyến khích hoặc nếu tôi nên thử một số con đường khác.

Câu trả lời:


135

subprocess.Popen thực hiện chính xác những gì bạn muốn.

from subprocess import Popen
p = Popen(['watch', 'ls']) # something long running
# ... do other stuff while subprocess is running
p.terminate()

(Chỉnh sửa để hoàn thành câu trả lời từ các bình luận)

Phiên bản Popen có thể thực hiện nhiều việc khác nhau như bạn có thể poll()làm để xem liệu nó có còn chạy hay không và bạn có thể communicate()gửi dữ liệu trên stdin và đợi nó kết thúc.


4
Bạn cũng có thể sử dụng thăm dò ý kiến ​​() để kiểm tra xem quá trình con đã kết thúc hay chưa, hoặc sử dụng hàm wait () để đợi quá trình kết thúc.
Adam Rosenfield

Adam, rất đúng, mặc dù có thể tốt hơn nếu sử dụng giao tiếp () để chờ đợi vì điều đó có khả năng xử lý bộ đệm vào / ra tốt hơn và có những tình huống mà lũ lụt này có thể chặn.
Ali Afshar

Adam: docs nói "Cảnh báo Điều này sẽ bế tắc nếu quy trình con tạo đủ đầu ra cho một đường ống stdout hoặc stderr để nó chặn chờ bộ đệm đường ống hệ điều hành chấp nhận thêm dữ liệu. Sử dụng giao tiếp () để tránh điều đó."
Ali Afshar

14
Tuy nhiên, giao tiếp () và wait () đang chặn các hoạt động. Bạn sẽ không song song hóa các lệnh như OP dường như hỏi bạn có sử dụng chúng hay không.
cdleary

1
Cdleary hoàn toàn chính xác, cần nhắc lại rằng giao tiếp và đợi do chặn, nên chỉ thực hiện khi bạn đang chờ mọi thứ tắt. (Mà bạn thực sự nên làm gì để được well-behaved)
Ali Afshar

48

Nếu bạn muốn chạy nhiều quy trình song song và sau đó xử lý chúng khi chúng mang lại kết quả, bạn có thể sử dụng tính năng thăm dò như sau:

from subprocess import Popen, PIPE
import time

running_procs = [
    Popen(['/usr/bin/my_cmd', '-i %s' % path], stdout=PIPE, stderr=PIPE)
    for path in '/tmp/file0 /tmp/file1 /tmp/file2'.split()]

while running_procs:
    for proc in running_procs:
        retcode = proc.poll()
        if retcode is not None: # Process finished.
            running_procs.remove(proc)
            break
        else: # No process is done, wait a bit and check again.
            time.sleep(.1)
            continue

    # Here, `proc` has finished with return code `retcode`
    if retcode != 0:
        """Error handling."""
    handle_results(proc.stdout)

Luồng điều khiển ở đó hơi phức tạp vì tôi đang cố làm cho nó nhỏ lại - bạn có thể cấu trúc lại theo sở thích của mình. :-)

Điều này có lợi thế là phục vụ các yêu cầu hoàn thiện sớm trước. Nếu bạn gọi communicatequy trình đang chạy đầu tiên và quá trình đó chạy lâu nhất, thì các quy trình đang chạy khác sẽ ở đó không hoạt động khi bạn có thể xử lý kết quả của chúng.


3
@Tino Nó phụ thuộc vào cách bạn xác định bận-đợi. Xem Sự khác biệt giữa bận-đợi và bỏ phiếu là gì?
Piotr Dobrogost

1
Có cách nào để thăm dò ý kiến ​​một tập hợp các quy trình không chỉ một?
Piotr Dobrogost

1
lưu ý: nó có thể bị treo nếu một quá trình tạo ra đủ đầu ra. Bạn nên sử dụng stdout đồng thời nếu bạn sử dụng PIPE (có (quá nhiều nhưng không đủ) cảnh báo trong tài liệu của quy trình con về nó).
jfs 22/11/12

@PiotrDobrogost: bạn có thể sử dụng os.waitpidtrực tiếp cho phép kiểm tra xem có bất kỳ quy trình con nào đã thay đổi trạng thái của nó hay không.
jfs 21/12/13

5
sử dụng ['/usr/bin/my_cmd', '-i', path]thay vì['/usr/bin/my_cmd', '-i %s' % path]
jfs

11

Điều tôi băn khoăn là liệu [os.system ()] này có phải là cách thích hợp để thực hiện một việc như vậy không?

Không, os.system()không phải là cách thích hợp. Đó là lý do tại sao mọi người nói để sử dụng subprocess.

Để biết thêm thông tin, hãy đọc http://docs.python.org/library/os.html#os.system

Mô-đun quy trình con cung cấp các phương tiện mạnh mẽ hơn để tạo ra các quy trình mới và truy xuất kết quả của chúng; sử dụng mô-đun đó sẽ thích hợp hơn sử dụng chức năng này. Sử dụng mô-đun quy trình con. Đặc biệt kiểm tra phần Thay thế các chức năng cũ hơn bằng phần Mô-đun quy trình con.


8

Tôi đã thành công tốt đẹp với mô-đun asyncproc , mô-đun này xử lý tốt đầu ra từ các quy trình. Ví dụ:

import os
from asynproc import Process
myProc = Process("myprogram.app")

while True:
    # check to see if process has ended
    poll = myProc.wait(os.WNOHANG)
    if poll is not None:
        break
    # print any new output
    out = myProc.read()
    if out != "":
        print out

cái này có ở đâu trên github không?
Nick

Đó là giấy phép gpl, vì vậy tôi chắc chắn rằng nó có trên đó nhiều lần. Đây là một: github.com/albertz/helpers/blob/master/asyncproc.py
Noah

Tôi đã thêm ý chính với một số sửa đổi để làm cho nó hoạt động với python3. (chủ yếu thay thế str bằng byte). Xem gist.github.com/grandemk/cbc528719e46b5a0ffbd07e3054aab83
Tic

1
Ngoài ra, bạn cần đọc đầu ra một lần nữa sau khi ra khỏi vòng lặp, nếu không bạn sẽ mất một phần đầu ra.
Tic

7

Sử dụng pexpect với các đường đọc không chặn là một cách khác để thực hiện việc này. Pexpect giải quyết các vấn đề về bế tắc, cho phép bạn dễ dàng chạy các quy trình trong nền và cung cấp các cách dễ dàng để có các lệnh gọi lại khi quy trình của bạn tạo ra các chuỗi được xác định trước và nói chung làm cho việc tương tác với quy trình dễ dàng hơn nhiều.


4

Xem xét "Tôi không cần phải đợi nó quay trở lại", một trong những giải pháp dễ dàng nhất sẽ là:

subprocess.Popen( \
    [path_to_executable, arg1, arg2, ... argN],
    creationflags = subprocess.CREATE_NEW_CONSOLE,
).pid

Nhưng ... Theo những gì tôi đọc, đây không phải là "cách thích hợp để thực hiện một việc như vậy" vì các rủi ro bảo mật được tạo ra bởi subprocess.CREATE_NEW_CONSOLE cờ .

Điều quan trọng xảy ra ở đây là sử dụng subprocess.CREATE_NEW_CONSOLEđể tạo bảng điều khiển mới và .pid(trả về ID tiến trình để bạn có thể kiểm tra chương trình sau này nếu muốn) để không phải đợi chương trình hoàn thành công việc của nó.


3

Tôi gặp vấn đề tương tự khi cố gắng kết nối với thiết bị đầu cuối 3270 bằng phần mềm tạo tập lệnh s3270 bằng Python. Bây giờ tôi đang giải quyết vấn đề với một lớp con của Quy trình mà tôi tìm thấy ở đây:

http://code.activestate.com/recipes/440554/

Và đây là mẫu được lấy từ tệp:

def recv_some(p, t=.1, e=1, tr=5, stderr=0):
    if tr < 1:
        tr = 1
    x = time.time()+t
    y = []
    r = ''
    pr = p.recv
    if stderr:
        pr = p.recv_err
    while time.time() < x or r:
        r = pr()
        if r is None:
            if e:
                raise Exception(message)
            else:
                break
        elif r:
            y.append(r)
        else:
            time.sleep(max((x-time.time())/tr, 0))
    return ''.join(y)

def send_all(p, data):
    while len(data):
        sent = p.send(data)
        if sent is None:
            raise Exception(message)
        data = buffer(data, sent)

if __name__ == '__main__':
    if sys.platform == 'win32':
        shell, commands, tail = ('cmd', ('dir /w', 'echo HELLO WORLD'), '\r\n')
    else:
        shell, commands, tail = ('sh', ('ls', 'echo HELLO WORLD'), '\n')

    a = Popen(shell, stdin=PIPE, stdout=PIPE)
    print recv_some(a),
    for cmd in commands:
        send_all(a, cmd + tail)
        print recv_some(a),
    send_all(a, 'exit' + tail)
    print recv_some(a, e=0)
    a.wait()

3

Câu trả lời được chấp nhận là rất cũ.

Tôi đã tìm thấy một câu trả lời hiện đại hơn ở đây:

https://kevinmccarthy.org/2016/07/25/streaming-subprocess-stdin-and-stdout-with-asyncio-in-python/

và thực hiện một số thay đổi:

  1. làm cho nó hoạt động trên windows
  2. làm cho nó hoạt động với nhiều lệnh
import sys
import asyncio

if sys.platform == "win32":
    asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())


async def _read_stream(stream, cb):
    while True:
        line = await stream.readline()
        if line:
            cb(line)
        else:
            break


async def _stream_subprocess(cmd, stdout_cb, stderr_cb):
    try:
        process = await asyncio.create_subprocess_exec(
            *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
        )

        await asyncio.wait(
            [
                _read_stream(process.stdout, stdout_cb),
                _read_stream(process.stderr, stderr_cb),
            ]
        )
        rc = await process.wait()
        return process.pid, rc
    except OSError as e:
        # the program will hang if we let any exception propagate
        return e


def execute(*aws):
    """ run the given coroutines in an asyncio loop
    returns a list containing the values returned from each coroutine.
    """
    loop = asyncio.get_event_loop()
    rc = loop.run_until_complete(asyncio.gather(*aws))
    loop.close()
    return rc


def printer(label):
    def pr(*args, **kw):
        print(label, *args, **kw)

    return pr


def name_it(start=0, template="s{}"):
    """a simple generator for task names
    """
    while True:
        yield template.format(start)
        start += 1


def runners(cmds):
    """
    cmds is a list of commands to excecute as subprocesses
    each item is a list appropriate for use by subprocess.call
    """
    next_name = name_it().__next__
    for cmd in cmds:
        name = next_name()
        out = printer(f"{name}.stdout")
        err = printer(f"{name}.stderr")
        yield _stream_subprocess(cmd, out, err)


if __name__ == "__main__":
    cmds = (
        [
            "sh",
            "-c",
            """echo "$SHELL"-stdout && sleep 1 && echo stderr 1>&2 && sleep 1 && echo done""",
        ],
        [
            "bash",
            "-c",
            "echo 'hello, Dave.' && sleep 1 && echo dave_err 1>&2 && sleep 1 && echo done",
        ],
        [sys.executable, "-c", 'print("hello from python");import sys;sys.exit(2)'],
    )

    print(execute(*runners(cmds)))

Không chắc rằng các lệnh ví dụ sẽ hoạt động hoàn hảo trên hệ thống của bạn và nó không xử lý các lỗi kỳ lạ, nhưng mã này chứng minh một cách để chạy nhiều quy trình con bằng cách sử dụng asyncio và phát trực tiếp đầu ra.


Tôi đã thử nghiệm này trên CPython 3.7.4 chạy trên cửa sổ và CPython 3.7.3 chạy trên Ubuntu WSL và mẹ đẻ Alpine Linux
Terrel Shumway


1

Có một số câu trả lời ở đây nhưng không có câu trả lời nào trong số chúng đáp ứng các yêu cầu dưới đây của tôi:

  1. Tôi không muốn đợi lệnh kết thúc hoặc làm ô nhiễm thiết bị đầu cuối của mình với các đầu ra quy trình con.

  2. Tôi muốn chạy tập lệnh bash với chuyển hướng.

  3. Tôi muốn hỗ trợ đường ống trong tập lệnh bash của mình (ví dụ find ... | tar ...).

Sự kết hợp duy nhất đáp ứng các yêu cầu trên là:

subprocess.Popen(['./my_script.sh "arg1" > "redirect/path/to"'],
                 stdout=subprocess.PIPE, 
                 stderr=subprocess.PIPE,
                 shell=True)

0

Điều này được đề cập trong Các ví dụ về quy trình con Python 3 trong "Chờ lệnh kết thúc không đồng bộ":

import asyncio

proc = await asyncio.create_subprocess_exec(
    'ls','-lha',
    stdout=asyncio.subprocess.PIPE,
    stderr=asyncio.subprocess.PIPE)

# do something else while ls is working

# if proc takes very long to complete, the CPUs are free to use cycles for 
# other processes
stdout, stderr = await proc.communicate()

Quá trình sẽ bắt đầu chạy ngay sau khi await asyncio.create_subprocess_exec(...)hoàn thành. Nếu nó vẫn chưa kết thúc vào thời điểm bạn gọi await proc.communicate(), nó sẽ đợi ở đó để cung cấp cho bạn trạng thái đầu ra của bạn. Nếu nó đã hoàn thành, proc.communicate()sẽ trở lại ngay lập tức.

Ý chính ở đây tương tự như câu trả lời của Terrels nhưng tôi nghĩ câu trả lời của Terrels có vẻ phức tạp quá mức.

Xem asyncio.create_subprocess_execđể biết thêm thông tin.

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.