Python'dan eşzamansız olarak harici bir komutu nasıl çalıştırabilirim?


120

Bir Python betiğinden eşzamansız olarak bir kabuk komutu çalıştırmam gerekiyor. Bununla demek istediğim, harici komut kapandığında ve yapması gereken her şeyi yaparken Python betiğimin çalışmaya devam etmesini istiyorum.

Bu yazıyı okudum:

Python'da harici bir komut çağırma

Daha sonra gittim ve bazı testler yaptım ve komutun sonunda os.system()kullanmam koşuluyla işi yapacağım, &böylece geri dönmesini beklemek zorunda kalmayacağım. Merak ettiğim şey, böyle bir şeyi başarmanın doğru yolu bu mu? Denedim commands.call()ama benim için çalışmayacak çünkü harici komutu engelliyor.

Bunun os.system()için kullanmanın tavsiye edilip edilmediğini veya başka bir yol denemem gerektiğini lütfen bana bildirin .

Yanıtlar:


135

subprocess.Popen tam olarak istediğinizi yapar.

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

(Yanıtı yorumlardan tamamlamak için düzenleyin)

Popen örneği poll(), hala çalışıp çalışmadığını görmek için sizin yaptığınız gibi çeşitli başka şeyler yapabilir communicate()ve bununla stdin üzerinden veri gönderebilir ve sona ermesini bekleyebilirsiniz.


4
Ayrıca, alt sürecin sonlandırılıp sonlandırılmadığını kontrol etmek için anket () kullanabilir veya sonlanmasını beklemek için wait () kullanabilirsiniz.
Adam Rosenfield

Adam, çok doğru, beklemek için iletişim () kullanmak daha iyi olabilir, çünkü bu, giriş / çıkış tamponlarının daha iyi işlenmesine sahiptir ve taşkınların bunları engelleyebileceği durumlar vardır.
Ali Afşar

Adam: dokümanlar "Uyarı Bu, alt süreç bir stdout veya stderr borusuna yeterli çıktı üretirse kilitlenecektir, öyle ki OS boru arabelleğinin daha fazla veriyi kabul etmesini engeller. Bundan kaçınmak için communication () kullanın."
Ali Afshar

14
communication () ve wait () ise işlemleri engelliyor. OP'nin onları kullanıp kullanmadığınızı sorduğu gibi komutları paralel hale getirmeyeceksiniz.
cdleary

1
Cdleary kesinlikle doğru, iletişim kurmak ve beklemek blok yapmaktan bahsetmek gerekir, bu yüzden bunu yalnızca bir şeylerin kapanmasını beklerken yapın. (İyi davranmak için gerçekten yapmalısın)
Ali Afşar

48

Birçok işlemi paralel olarak çalıştırmak ve daha sonra sonuç verdiklerinde bunları işlemek istiyorsanız, aşağıdaki gibi yoklamayı kullanabilirsiniz:

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)

Oradaki kontrol akışı biraz kıvrımlı çünkü onu küçültmeye çalışıyorum - zevkinize göre yeniden düzenleme yapabilirsiniz. :-)

Bu, önce erken bitirme taleplerini karşılama avantajına sahiptir. communicateİlk çalışan süreci çağırırsanız ve bunun en uzun süre çalıştığı ortaya çıkarsa, diğer çalışan süreçler siz onların sonuçlarını ele alırken orada boşta dururlar.


3
@Tino Meşgul beklemeyi nasıl tanımladığınıza bağlıdır. Bkz meşgul-bekle ve yoklama arasındaki fark nedir?
Piotr Dobrogost

1
Bir dizi süreci yoklamanın bir yolu var mı?
Piotr Dobrogost

1
not: bir işlem yeterli çıktı üretirse askıda kalabilir. PIPE kullanıyorsanız aynı anda stdout tüketmelisiniz (bununla ilgili alt işlem belgelerinde (çok fazla ama yeterli değil) uyarı var).
jfs

@PiotrDobrogost: herhangi bir alt sürecin durumunu değiştirip değiştirmediğini os.waitpidkontrol etmek için doğrudan kullanabilirsiniz .
jfs

5
['/usr/bin/my_cmd', '-i', path]yerine kullanın['/usr/bin/my_cmd', '-i %s' % path]
jfs

11

Merak ettiğim şey, bu [os.system ()] böyle bir şeyi başarmanın doğru yolu olup olmadığıdır?

Hayır os.system(), doğru yol değil. Bu yüzden herkes kullan diyor subprocess.

Daha fazla bilgi için http://docs.python.org/library/os.html#os.system okuyun

Alt süreç modülü, yeni süreçler üretmek ve sonuçlarını almak için daha güçlü olanaklar sağlar; bu modülün kullanılması, bu işlevi kullanmaya tercih edilir. Alt işlem modülünü kullanın. Özellikle Alt İşlem Modülü ile Eski İşlevlerin Değiştirilmesi bölümünü kontrol edin.


8

Süreçlerin çıktılarıyla güzelce ilgilenen asyncproc modülüyle iyi bir başarı elde ettim . Örneğin:

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

bu github'da herhangi bir yerde mi?
Nick

Bu gpl lisansı, bu yüzden birçok kez orada olduğundan eminim. İşte biri: github.com/albertz/helpers/blob/master/asyncproc.py
Noah

Python3 ile çalışmasını sağlamak için bazı değişikliklerle bir özet ekledim. (çoğunlukla dizeyi baytlarla değiştirir). Bkz. Gist.github.com/grandemk/cbc528719e46b5a0ffbd07e3054aab83
Tic

1
Ayrıca, döngüden çıktıktan sonra çıktıyı bir kez daha okumanız gerekir, yoksa çıktının bir kısmını kaybedersiniz.
Tic

7

Pexpect'i engellemeyen okuma satırlarıyla kullanmak bunu yapmanın başka bir yoludur. Pexpect, kilitlenme sorunlarını çözer, süreçleri arka planda kolayca çalıştırmanıza olanak tanır ve işleminiz önceden tanımlanmış dizeleri dışarı çıkardığında geri aramalar yapmanın kolay yollarını sunar ve genellikle işlemle etkileşimi çok daha kolay hale getirir.


4

"Geri dönmesini beklememe gerek yok" düşüncesiyle, en kolay çözümlerden biri şudur:

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

Ama ... Okuduklarıma göre bu, subprocess.CREATE_NEW_CONSOLEbayrakların yarattığı güvenlik riskleri nedeniyle "böyle bir şeyi başarmanın doğru yolu" değil .

Burada gerçekleşen temel şeyler, subprocess.CREATE_NEW_CONSOLEyeni konsol oluşturmak ve .pid(isterseniz programı daha sonra kontrol edebilmeniz için işlem kimliğini döndürmek) kullanmaktır, böylece programın işini bitirmesini beklememek.


3

Python'daki s3270 komut dosyası yazılımını kullanarak bir 3270 terminaline bağlanmaya çalışırken aynı sorunu yaşıyorum. Şimdi sorunu burada bulduğum bir Süreç alt sınıfıyla çözüyorum:

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

Ve işte dosyadan alınan örnek:

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

Kabul edilen cevap çok eskidir.

Burada daha iyi ve modern bir cevap buldum:

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

ve bazı değişiklikler yaptı:

  1. pencerelerde çalışmasını sağla
  2. birden çok komutla çalışmasını sağlayın
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)))

Örnek komutların sisteminizde mükemmel bir şekilde çalışması olası değildir ve tuhaf hataları ele almaz, ancak bu kod, asyncio kullanarak birden çok alt işlemi çalıştırmanın ve çıktıyı akışa almanın bir yolunu gösterir.


Bunu Windows üzerinde çalışan cpython 3.7.4 ve Ubuntu WSL ve yerel Alpine Linux üzerinde çalışan cpython 3.7.3 üzerinde test ettim
Terrel Shumway


1

Burada birkaç cevap var ancak hiçbiri aşağıdaki gereksinimleri karşılamıyor:

  1. Komutun terminalimi alt işlem çıktılarıyla bitirmesini veya kirletmesini beklemek istemiyorum.

  2. Yeniden yönlendirmelerle bash betiğini çalıştırmak istiyorum.

  3. Bash betiğim içinde borulamayı desteklemek istiyorum (örneğin find ... | tar ...).

Yukarıdaki gereksinimleri karşılayan tek kombinasyon şudur:

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

0

Bu, Python 3 Alt İşlem Örnekleri tarafından "Eşzamansız olarak sonlandırmak için komut için bekle" başlığı altında ele alınmıştır :

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

İşlem await asyncio.create_subprocess_exec(...)tamamlanır tamamlanmaz çalışmaya başlayacaktır . Aradığınız zamana kadar bitmemişse await proc.communicate(), size çıktı durumunuzu vermek için orada bekleyecektir. Tamamlandığında ise, proc.communicate()derhal dönecektir.

Buradaki ana fikir Terrels cevabına benzer ancak Terrels'in cevabının işleri aşırı karmaşık düşünüyorum.

Daha asyncio.create_subprocess_execfazla bilgi için bakın .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.