Cara mengakhiri subproses python diluncurkan dengan shell = True

308

Saya meluncurkan subproses dengan perintah berikut:

p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)

Namun, ketika saya mencoba membunuh menggunakan:

p.terminate()

atau

p.kill()

Perintah terus berjalan di latar belakang, jadi saya bertanya-tanya bagaimana saya bisa benar-benar menghentikan proses.

Perhatikan bahwa ketika saya menjalankan perintah dengan:

p = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)

Itu berhasil berakhir ketika mengeluarkan p.terminate().

pengguna175259
sumber
Seperti apa cmdpenampilanmu? Ini mungkin berisi perintah yang memicu beberapa proses untuk dimulai. Jadi tidak jelas proses yang Anda bicarakan.
Robert Siemer
1
tidak harus shell=Truemembuat perbedaan besar?
Charlie Parker

Jawaban:

410

Gunakan grup proses untuk mengaktifkan pengiriman sinyal ke semua proses dalam grup. Untuk itu, Anda harus melampirkan id sesi ke proses induk dari proses spawned / child, yang merupakan shell dalam kasus Anda. Ini akan menjadikannya pemimpin kelompok dalam proses. Jadi sekarang, ketika sinyal dikirim ke pemimpin grup proses, itu dikirim ke semua proses anak dari grup ini.

Ini kodenya:

import os
import signal
import subprocess

# The os.setsid() is passed in the argument preexec_fn so
# it's run after the fork() and before  exec() to run the shell.
pro = subprocess.Popen(cmd, stdout=subprocess.PIPE, 
                       shell=True, preexec_fn=os.setsid) 

os.killpg(os.getpgid(pro.pid), signal.SIGTERM)  # Send the signal to all the process groups
mouad
sumber
2
@PiotrDobrogost: Sayangnya tidak, karena os.setsidtidak tersedia di windows ( docs.python.org/library/os.html#os.setsid ), saya tidak tahu apakah ini dapat membantu tetapi Anda dapat melihat di sini ( bugs.python.org / issue5115 ) untuk beberapa wawasan tentang bagaimana melakukannya.
mouad
6
Bagaimana subprocess.CREATE_NEW_PROCESS_GROUPhubungannya dengan ini?
Piotr Dobrogost
11
sugggest pengujian kami yang setsid! = setpgid, dan os.pgkill hanya akan membunuh subproses yang masih memiliki id grup proses yang sama. proses yang telah mengubah grup proses tidak terbunuh, meskipun mereka mungkin masih memiliki sesi id yang sama ...
hwjp
4
Saya tidak akan merekomendasikan melakukan os.setsid (), karena memiliki efek lain juga. Antara lain, ia memutus TTY yang mengendalikan dan menjadikan proses baru sebagai pemimpin kelompok proses. Lihat win.tue.nl/~aeb/linux/lk/lk-10.html
parasietje
92
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
p.kill()

p.kill()akhirnya membunuh proses shell dan cmdmasih berjalan.

Saya menemukan perbaikan yang mudah dengan:

p = subprocess.Popen("exec " + cmd, stdout=subprocess.PIPE, shell=True)

Ini akan menyebabkan cmd mewarisi proses shell, alih-alih membuat shell meluncurkan proses anak, yang tidak terbunuh. p.pidakan menjadi id dari proses cmd Anda.

p.kill() harus bekerja.

Saya tidak tahu apa efek ini pada pipa Anda.

Bryant Hansen
sumber
1
Solusi yang bagus dan ringan untuk * nix, terima kasih! Bekerja di Linux, harus bekerja untuk Mac juga.
MarSoft
Solusi yang sangat bagus Jika cmd Anda adalah pembungkus skrip shell untuk sesuatu yang lain, panggil biner terakhir di sana dengan exec juga agar hanya memiliki satu subproses.
Nicolinux
1
Ini indah. Saya telah mencoba mencari cara untuk menelurkan dan membunuh subproses per ruang kerja di Ubuntu. Jawaban ini membantu saya. Seandainya saya bisa membesarkannya lebih dari satu kali
Sergiy Kolodyazhnyy
2
ini tidak berfungsi jika titik koma digunakan dalam cmd
gnr
@speedyrazor - Tidak berfungsi di Windows10. Saya pikir os jawaban spesifik harus ditandai dengan jelas seperti itu.
DarkLight
48

Jika Anda dapat menggunakan psutil , maka ini berfungsi dengan baik:

import subprocess

import psutil


def kill(proc_pid):
    process = psutil.Process(proc_pid)
    for proc in process.children(recursive=True):
        proc.kill()
    process.kill()


proc = subprocess.Popen(["infinite_app", "param"], shell=True)
try:
    proc.wait(timeout=3)
except subprocess.TimeoutExpired:
    kill(proc.pid)
Jovik
sumber
3
AttributeError: 'Process' object has no attribute 'get_childrenuntuk pip install psutil.
d33tah
3
Saya pikir get_children () harus anak-anak (). Tapi itu tidak berhasil untuk saya di Windows, prosesnya masih ada.
Godsmith
3
@Godsmith - psutil API telah berubah dan Anda benar: anak-anak () melakukan hal yang sama seperti get_children () dulu. Jika tidak bekerja di Windows, maka Anda mungkin ingin membuat tiket bug di GitHub
Jovik
24

Saya bisa melakukannya dengan menggunakan

from subprocess import Popen

process = Popen(command, shell=True)
Popen("TASKKILL /F /PID {pid} /T".format(pid=process.pid))

itu membunuh cmd.exedan program yang saya berikan perintah untuk.

(Di Windows)

SPratap
sumber
Atau gunakan nama proses : Popen("TASKKILL /F /IM " + process_name), jika Anda tidak memilikinya, Anda bisa mendapatkannya dari commandparameter.
zvi
12

Ketika shell=Trueshell adalah proses anak, dan perintahnya adalah anak-anaknya. Jadi, ada SIGTERMatau SIGKILLakan membunuh cangkang tetapi tidak proses anaknya, dan saya tidak ingat cara yang baik untuk melakukannya. Cara terbaik yang dapat saya pikirkan adalah menggunakan shell=False, jika tidak ketika Anda membunuh proses shell induk, itu akan meninggalkan proses shell yang mati.

Sai Venkat
sumber
8

Tidak satu pun dari jawaban ini bekerja untuk saya sehingga saya meninggalkan kode yang berhasil. Dalam kasus saya bahkan setelah membunuh proses dengan .kill()dan mendapatkan .poll()kode kembali proses tersebut tidak berakhir.

Berikut subprocess.Popen dokumentasi :

"... untuk membersihkan aplikasi yang berperilaku baik harus membunuh proses anak dan menyelesaikan komunikasi ..."

proc = subprocess.Popen(...)
try:
    outs, errs = proc.communicate(timeout=15)
except TimeoutExpired:
    proc.kill()
    outs, errs = proc.communicate()

Dalam kasus saya, saya kehilangan proc.communicate()panggilan setelah proc.kill(). Ini membersihkan proses stdin, stdout ... dan menghentikan proses.

epinal
sumber
Solusi ini tidak berfungsi untuk saya di linux dan python 2.7
user9869932
@xyz Itu berhasil bagi saya di Linux dan python 3.5. Periksa dokumen untuk python 2.7
epinal
@espinal, terima kasih, ya. Itu mungkin masalah linux. Ini Raspbian linux yang berjalan pada Raspberry 3
user9869932
5

Seperti yang dikatakan Sai, shell adalah anak, jadi sinyal dicegat olehnya - cara terbaik yang saya temukan adalah menggunakan shell = False dan gunakan shlex untuk membagi baris perintah:

if isinstance(command, unicode):
    cmd = command.encode('utf8')
args = shlex.split(cmd)

p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

Maka p.kill () dan p.terminate () harus bekerja seperti yang Anda harapkan.

Matt Billenstein
sumber
1
Dalam kasus saya itu tidak terlalu membantu mengingat bahwa cmd adalah "cd path && zsync etc etc". Jadi itu benar-benar membuat perintah gagal!
user175259
4
Gunakan jalur absolut alih-alih mengubah direktori ... Opsional os.chdir (...) ke direktori itu ...
Matt Billenstein
Kemampuan untuk mengubah direktori kerja untuk proses anak sudah ada di dalamnya. Lewati saja cwdargumennya Popen.
Jonathon Reinhart
Saya menggunakan shlex, tapi tetap saja masalahnya, membunuh tidak membunuh proses anak.
hungryWolf
1

apa yang saya rasa bisa kita gunakan:

import os
import signal
import subprocess
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)

os.killpg(os.getpgid(pro.pid), signal.SIGINT)

ini tidak akan membunuh semua tugas Anda tetapi proses dengan p.pid

Nilesh K.
sumber
0

Saya tahu ini adalah pertanyaan lama tetapi ini dapat membantu seseorang mencari metode yang berbeda. Ini yang saya gunakan di windows untuk mematikan proses yang saya panggil.

si = subprocess.STARTUPINFO()
si.dwFlags |= subprocess.STARTF_USESHOWWINDOW
subprocess.call(["taskkill", "/IM", "robocopy.exe", "/T", "/F"], startupinfo=si)

/ IM adalah nama gambar, Anda juga dapat melakukan / PID jika Anda mau. / T membunuh proses serta proses anak. / F force menghentikannya. si, seperti yang saya atur, adalah bagaimana Anda melakukan ini tanpa menunjukkan jendela CMD. Kode ini digunakan dalam python 3.

Zx4161
sumber
0

Kirim sinyal ke semua proses dalam grup

    self.proc = Popen(commands, 
            stdout=PIPE, 
            stderr=STDOUT, 
            universal_newlines=True, 
            preexec_fn=os.setsid)

    os.killpg(os.getpgid(self.proc.pid), signal.SIGHUP)
    os.killpg(os.getpgid(self.proc.pid), signal.SIGTERM)
rogers.wang
sumber
0

Saya belum melihat ini disebutkan di sini, jadi saya meletakkannya di sana kalau-kalau seseorang membutuhkannya. Jika yang ingin Anda lakukan adalah memastikan bahwa subproses Anda berakhir dengan sukses, Anda bisa memasukkannya ke dalam manajer konteks. Sebagai contoh, saya ingin printer standar saya untuk mencetak gambar dan menggunakan manajer konteks memastikan bahwa subproses dihentikan.

import subprocess

with open(filename,'rb') as f:
    img=f.read()
with subprocess.Popen("/usr/bin/lpr", stdin=subprocess.PIPE) as lpr:
    lpr.stdin.write(img)
print('Printed image...')

Saya percaya metode ini juga lintas platform.

Jaiyam Sharma
sumber
Saya gagal melihat bagaimana kode di sini menghentikan suatu proses. Bisakah Anda mengklarifikasi?
DarkLight
Saya telah menyatakan dengan jelas bahwa jika semua yang Anda ingin lakukan memastikan bahwa proses Anda telah dihentikan sebelum melanjutkan ke baris kode Anda berikutnya, Anda dapat menggunakan metode ini. Saya tidak mengklaim bahwa itu menghentikan proses.
Jaiyam Sharma
Jika manajer konteks "memastikan bahwa proses Anda telah dihentikan", karena Anda telah menyatakan dengan jelas, maka Anda melakukan klaim bahwa itu mengakhiri proses. Apakah itu? Bahkan ketika proses diluncurkan dengan shell=True, sesuai pertanyaan? Yang tidak ada dalam kode Anda.
anon
anon, terima kasih telah menunjukkan penggunaan kata 'terminated' yang membingungkan. Manajer konteks menunggu proses selesai sebelum melanjutkan ke pernyataan cetak pada baris terakhir. Ini berbeda dengan menghentikan proses secara instan menggunakan sesuatu seperti sigterm. Anda bisa mendapatkan hasil yang sama menggunakan utas dan panggilan thread.join(). Semoga ini jelas.
Jaiyam Sharma