วิธียุติกระบวนการย่อยของ python ที่เปิดตัวด้วย shell = True


308

ฉันเปิดตัว subprocess ด้วยคำสั่งต่อไปนี้:

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

อย่างไรก็ตามเมื่อฉันพยายามฆ่าโดยใช้:

p.terminate()

หรือ

p.kill()

คำสั่งยังคงทำงานในพื้นหลังดังนั้นฉันสงสัยว่าฉันจะยุติกระบวนการได้อย่างไร

โปรดทราบว่าเมื่อฉันเรียกใช้คำสั่งด้วย:

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

p.terminate()มันไม่ยุติเรียบร้อยแล้วเมื่อออก


คุณcmdมีลักษณะอย่างไร มันอาจมีคำสั่งที่ก่อให้เกิดกระบวนการหลายอย่างที่จะเริ่มต้น ดังนั้นจึงไม่ชัดเจนว่าคุณพูดถึงกระบวนการอะไร
Robert Siemer


1
ไม่ได้shell=Trueสร้างความแตกต่างใหญ่?
Charlie Parker

คำตอบ:


410

ใช้กลุ่มกระบวนการเพื่อเปิดใช้งานการส่งสัญญาณไปยังกระบวนการทั้งหมดในกลุ่ม เพื่อที่คุณควรแนบรหัสเซสชั่นเข้ากับกระบวนการ parent ของกระบวนการ spawned / child ซึ่งเป็น shell ในกรณีของคุณ สิ่งนี้จะทำให้มันเป็นผู้นำกลุ่มของกระบวนการ ดังนั้นเมื่อสัญญาณถูกส่งไปยังหัวหน้ากลุ่มกระบวนการมันจะถูกส่งไปยังกระบวนการลูกทั้งหมดของกลุ่มนี้

นี่คือรหัส:

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

2
@PiotrDobrogost: ไม่น่าเสียดายเพราะos.setsidไม่มีใน windows ( docs.python.org/library/os.html#os.setsid ) ฉันไม่รู้ว่ามันจะช่วยได้ไหม แต่คุณสามารถดูได้ที่นี่ ( bugs.python.org / issue5115 ) สำหรับข้อมูลเชิงลึกเกี่ยวกับวิธีการทำ
mouad

6
วิธีการที่ไม่subprocess.CREATE_NEW_PROCESS_GROUPเกี่ยวข้องกับเรื่องนี้?
Piotr Dobrogost

11
การทดสอบของเรา Sugggests ที่ setsid! = setpgid และ os.pgkill ฆ่าเฉพาะกระบวนการย่อยที่ยังคงมี id กลุ่มกระบวนการเดียวกัน กระบวนการที่มีการเปลี่ยนแปลงกลุ่มกระบวนการจะไม่ถูกฆ่าแม้ว่าพวกเขาจะยังคงมีเซสชั่น id เดียวกัน ...
hwjp


4
ฉันจะไม่แนะนำให้ทำ os.setsid () เนื่องจากมันมีผลกระทบอื่น ๆ เช่นกัน มันแยกการควบคุม TTY ออกจากกันและทำให้กระบวนการใหม่เป็นผู้นำกลุ่มกระบวนการ ดูwin.tue.nl/~aeb/linux/lk/lk-10.html
parasietje

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

p.kill()จบลงด้วยการฆ่ากระบวนการเชลล์และcmdยังคงทำงานอยู่

ฉันพบวิธีแก้ไขที่สะดวกโดย:

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

สิ่งนี้จะทำให้ cmd สืบทอดกระบวนการเชลล์แทนที่จะให้เชลล์เรียกใช้งานกระบวนการลูกซึ่งไม่ได้ถูกฆ่า p.pidจะเป็นรหัสของกระบวนการ cmd ของคุณแล้ว

p.kill() ควรทำงาน.

ฉันไม่ทราบว่าสิ่งนี้จะมีผลต่อท่อของคุณ


1
โซลูชั่นที่ดีและเบาสำหรับ * ระวังขอบคุณ! ใช้งานได้กับ Linux ควรใช้กับ Mac ได้เช่นกัน
MarSoft

ทางออกที่ดีมาก หาก cmd ของคุณเป็น shell shell wrapper อย่างอื่นให้เรียก binary สุดท้ายที่นั่นด้วย exec ด้วยเพื่อให้มี subprocess เพียงอันเดียว
Nicolinux

1
นี่คือสิ่งที่สวยงาม ฉันพยายามหาวิธีวางไข่และฆ่า subprocess per workspace บน Ubuntu คำตอบนี้ช่วยฉัน หวังว่าฉันจะสามารถโหวตได้มากกว่าหนึ่งครั้ง
Sergiy Kolodyazhnyy

2
สิ่งนี้จะไม่ทำงานหากใช้เซมิโคลอนใน cmd
gnr

@speedyrazor - ไม่ทำงานบน Windows10 ฉันคิดว่าระบบปฏิบัติการเฉพาะคำตอบควรถูกทำเครื่องหมายอย่างชัดเจนเช่นนี้
DarkLight

48

หากคุณสามารถใช้psutilได้ผลดีมาก:

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)

3
AttributeError: 'Process' object has no attribute 'get_childrenpip install psutilสำหรับ
d33tah

3
ฉันคิดว่า get_children () ควรเป็นลูก () แต่มันไม่ได้ผลสำหรับฉันบน Windows กระบวนการยังคงอยู่ที่นั่น
Godsmith

3
@Godsmith - psutil API มีการเปลี่ยนแปลงและคุณพูดถูก: children ()ทำสิ่งเดียวกับ get_children () ถ้ามันไม่ทำงานบน Windows คุณอาจต้องการสร้างตั๋วบั๊กในGitHub
Jovik

24

ฉันสามารถทำได้โดยใช้

from subprocess import Popen

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

มันฆ่า cmd.exeและโปรแกรมที่ฉันให้คำสั่ง

(บน Windows)


หรือใช้ชื่อกระบวนการ : Popen("TASKKILL /F /IM " + process_name)หากคุณไม่มีคุณสามารถหาได้จากcommandพารามิเตอร์
zvi

12

เมื่อshell=Trueเชลล์เป็นกระบวนการลูกและคำสั่งเป็นลูกของมัน ดังนั้นSIGTERMหรือSIGKILLจะฆ่าเปลือก แต่ไม่ใช่กระบวนการลูกของมันและฉันจำไม่ได้ว่าเป็นวิธีที่ดีในการทำมัน วิธีที่ดีที่สุดที่ฉันคิดได้คือการใช้shell=Falseมิฉะนั้นเมื่อคุณฆ่ากระบวนการเชลล์พาเรนต์มันจะทำให้กระบวนการเชลล์หมดอายุ


8

ไม่มีคำตอบใดที่เหมาะกับฉันดังนั้นฉันจึงทิ้งรหัสที่ใช้งานได้ ในกรณีของฉันแม้หลังจากฆ่ากระบวนการด้วย.kill()และรับ.poll()รหัสส่งคืนกระบวนการก็ไม่ได้ยุติลง

ทำตามsubprocess.Popen เอกสาร :

"... เพื่อล้างข้อมูลอย่างถูกต้องแอปพลิเคชันที่ทำงานได้ดีควรฆ่ากระบวนการลูกและจบการสื่อสาร ... "

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

ในกรณีของผมก็หายไปหลังจากเรียกproc.communicate() proc.kill()สิ่งนี้จะล้างกระบวนการ stdin, stdout ... และจะยกเลิกกระบวนการ


โซลูชันนี้ใช้งานไม่ได้สำหรับฉันใน linux และ python 2.7
user9869932

@xyz มันใช้งานได้สำหรับฉันใน Linux และ python 3.5 ตรวจสอบเอกสารสำหรับ python 2.7
epinal

@ ที่สุดขอบคุณใช่ มันอาจเป็นปัญหาของลินุกซ์ มันคือ Raspbian linux ที่ทำงานบน Raspberry 3
user9869932

5

ดังที่ Sai กล่าวว่าเชลล์คือลูกดังนั้นสัญญาณจึงถูกดัก - วิธีที่ดีที่สุดที่ฉันพบคือใช้ shell = False และใช้ shlex เพื่อแยกบรรทัดคำสั่ง:

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

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

จากนั้น p.kill () และ p.terminate () ควรทำงานตามที่คุณคาดหวัง


1
ในกรณีของฉันมันไม่ได้ช่วยจริงๆเพราะ cmd คือ "cd path && zsync etc etc" นั่นทำให้คำสั่งล้มเหลวจริง ๆ !
user175259

4
ใช้เส้นทางแน่นอนแทนของไดเรกทอรีเปลี่ยนแปลง ... เลือก os.chdir ( ... ) ไปยังไดเรกทอรีที่ ...
แมตต์ Billenstein

ความสามารถในการเปลี่ยนไดเรกทอรีการทำงานสำหรับกระบวนการลูกนั้นเป็นแบบในตัว เพียงแค่ผ่านอาร์กิวเมนต์cwd Popen
Jonathon Reinhart

ฉันใช้ shlex แต่ยังคงมีปัญหาอยู่การฆ่าไม่ได้ฆ่ากระบวนการลูก
hungryWolf

1

สิ่งที่ฉันรู้สึกเหมือนเราสามารถใช้:

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

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

สิ่งนี้จะไม่ฆ่างานทั้งหมดของคุณ แต่จะดำเนินการกับ p.pid


0

ฉันรู้ว่านี่เป็นคำถามเก่า แต่อาจช่วยให้ใครบางคนกำลังมองหาวิธีการที่แตกต่างกัน นี่คือสิ่งที่ฉันใช้บน windows เพื่อฆ่ากระบวนการที่ฉันเรียก

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

/ IM เป็นชื่อภาพคุณสามารถทำ / PID ได้หากต้องการ / T ฆ่ากระบวนการเช่นเดียวกับกระบวนการลูก / F แรงยุติลง si, ตามที่ฉันตั้งไว้, เป็นวิธีที่คุณทำโดยไม่แสดงหน้าต่าง CMD รหัสนี้ใช้ในหลาม 3


0

ส่งสัญญาณไปยังกระบวนการทั้งหมดในกลุ่ม

    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)

0

ฉันไม่ได้เห็นสิ่งนี้ที่กล่าวถึงที่นี่ดังนั้นฉันจึงวางมันไว้ในกรณีที่มีคนต้องการมัน หากสิ่งที่คุณต้องทำคือการทำให้แน่ใจว่ากระบวนการย่อยของคุณสิ้นสุดลงอย่างสมบูรณ์คุณสามารถวางไว้ในตัวจัดการบริบท ตัวอย่างเช่นฉันต้องการให้เครื่องพิมพ์มาตรฐานของฉันพิมพ์ภาพออกมาและใช้ตัวจัดการบริบทเพื่อให้แน่ใจว่ากระบวนการย่อยจะสิ้นสุดลง

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

ฉันเชื่อว่าวิธีนี้ยังข้ามแพลตฟอร์ม


ฉันล้มเหลวในการดูว่าโค้ดที่นี่ยุติกระบวนการอย่างไร คุณช่วยอธิบายได้ไหม
DarkLight

ฉันได้ระบุไว้อย่างชัดเจนว่าหากสิ่งที่คุณต้องการจะทำให้แน่ใจว่ากระบวนการของคุณสิ้นสุดลงก่อนที่จะดำเนินการตามบรรทัดถัดไปของรหัสของคุณคุณสามารถใช้วิธีนี้ได้ ฉันไม่ได้อ้างว่ามันยุติกระบวนการ
Jaiyam Sharma

หากตัวจัดการบริบท "ทำให้แน่ใจว่ากระบวนการของคุณสิ้นสุดลง" ตามที่คุณได้ระบุไว้อย่างชัดเจนคุณจะอ้างว่ามันยุติกระบวนการ มันใช่มั้ย แม้ในขณะที่กระบวนการเปิดตัวด้วยshell=Trueตามคำถาม? ซึ่งมันไม่ได้อยู่ในรหัสของคุณ
anon

อานนท์ขอขอบคุณสำหรับการชี้ให้เห็นถึงการใช้คำว่า 'ยุติ' ทำให้เกิดความสับสน ตัวจัดการบริบทรอให้กระบวนการย่อยเสร็จสมบูรณ์ก่อนที่จะไปยังคำสั่งการพิมพ์ในบรรทัดสุดท้าย สิ่งนี้แตกต่างจากการยกเลิกกระบวนการในทันทีโดยใช้บางอย่างเช่น sigterm thread.join()คุณอาจมีผลเหมือนกันโดยใช้ด้ายและเรียก หวังว่านี่จะเป็นการลบล้าง
Jaiyam Sharma
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.