วิธีที่ดีที่สุดในการส่งสัญญาณไปยังสมาชิกทุกคนของกลุ่มกระบวนการคืออะไร?


422

ฉันต้องการฆ่าต้นไม้กระบวนการทั้งหมด วิธีที่ดีที่สุดในการทำสิ่งนี้โดยใช้ภาษาสคริปต์ทั่วไปคืออะไร ฉันกำลังมองหาทางออกที่ง่าย


3
Zombies ควรจะหายไปเมื่อระบบ reaper ทำงานแม้ว่า ฉันจะยอมรับว่าฉันได้เห็นระบบที่ซอมบี้อู้อยู่ แต่นั่นผิดปกติ
Brian Knoblauch

7
บางครั้งพวกซอมบี้ที่ยังอ้อยอิ่งอยู่นั้นต้องรับผิดชอบต่อกิจกรรมที่น่ากลัว
User1

ใช้หนึ่งในคำสั่งchronosหรือ herodes
Michael Le Barbier Grünewald

8
kill $ (pstree <PID> -p -a -l | cut -d, -f2 | cut -d ''
-f1

@ MichaelLeBarbierGrünewaldคุณช่วยกรุณาลิงค์ไปยังโปรแกรมเหล่านั้นได้ไหม?
โฟมบิน

คำตอบ:


305

คุณไม่ได้พูดว่าทรีที่คุณต้องการฆ่าเป็นกลุ่มกระบวนการเดียวหรือไม่ (ในกรณีนี้มักจะเกิดจากการฟอร์กจากการเริ่มต้นเซิร์ฟเวอร์หรือบรรทัดคำสั่งเชลล์) คุณสามารถค้นหากลุ่มกระบวนการโดยใช้ GNU ps ดังนี้

 ps x -o  "%p %r %y %x %c "

หากเป็นกลุ่มกระบวนการที่คุณต้องการฆ่าเพียงใช้kill(1)คำสั่ง แต่แทนที่จะให้หมายเลขกระบวนการให้ลบค่าหมายเลขกลุ่มนั้น ตัวอย่างที่จะฆ่าทุกขั้นตอนในกลุ่ม 5112, kill -TERM -- -5112การใช้งาน


3
kill -74313 -bash: kill: 74313: ข้อมูลจำเพาะสัญญาณไม่ถูกต้องหากฉันเพิ่ม kill -15 -GPID มันทำงานได้อย่างสมบูรณ์
Adam Peck

51
ตามปกติด้วยคำสั่งเกือบทุกชนิดหากคุณต้องการอาร์กิวเมนต์ปกติที่ขึ้นต้นด้วย - จะไม่ถูกตีความว่าเป็นสวิตช์นำหน้าด้วย -: kill - -GPID
ysth

9
pgrepสามารถเสนอวิธีที่ง่ายกว่าในการค้นหา ID กลุ่มกระบวนการ ตัวอย่างเช่นการฆ่า my-script.sh kill -TERM -$(pgrep -o my-script.sh)ของกลุ่มกระบวนการการทำงาน
Josh Kelley

9
ดีกว่าดูที่stackoverflow.com/questions/392022/…มันเป็นทางออกที่ดีกว่าและถ้าคุณต้องการแสดงรายการ pids ของเด็กให้ใช้:ps -o pid --no-headers --ppid $PARENT_PID
Szymon Jeż

4
และถ้าคุณปรับเปลี่ยนรูปแบบเล็กน้อยและจัดเรียงคุณจะได้เห็นกระบวนการทั้งหมดที่จัดกลุ่มไว้อย่างดีและเริ่มต้นด้วย (อาจ) พาเรนต์กลุ่มในแต่ละกลุ่ม:ps x -o "%r %p %y %x %c" | sort -nk1,2
haridsv

199

ฆ่ากระบวนการทั้งหมดที่เป็นของแผนผังกระบวนการเดียวกันโดยใช้ID กลุ่มกระบวนการ ( PGID)

  • kill -- -$PGID     ใช้สัญญาณเริ่มต้น ( TERM= 15)
  • kill -9 -$PGID     ใช้สัญญาณKILL(9)

คุณสามารถดึงข้อมูลPGIDจากProcess-IDใด ๆ( PID) ของแผนผังกระบวนการเดียวกัน

  • kill -- -$(ps -o pgid= $PID | grep -o '[0-9]*')   (สัญญาณTERM)
  • kill -9 -$(ps -o pgid= $PID | grep -o '[0-9]*')   (สัญญาณKILL)

ขอขอบคุณเป็นพิเศษสำหรับtanagerและSpeakusสำหรับการสนับสนุน$PIDพื้นที่ที่เหลือและความเข้ากันได้ของ OSX

คำอธิบาย

  • kill -9 -"$PGID"=> ส่งสัญญาณ 9 ( KILL) ไปยังเด็กและหลานทั้งหมด ...
  • PGID=$(ps opgid= "$PID")=> เรียกกระบวนการกลุ่ม-IDจากกระบวนการ-IDของต้นไม้ที่ไม่เพียง แต่กระบวนการปกครอง-ID รูปแบบของps opgid= $PIDมีที่ps -o pgid --no-headers $PIDที่จะถูกแทนที่ด้วยpgid แต่: pgrp
    • psแทรกช่องว่างนำเมื่อPIDเป็นน้อยกว่าห้าตัวเลขและสอดคล้องขวาสังเกตเห็นโดยTanager คุณสามารถใช้ได้:
      PGID=$(ps opgid= "$PID" | tr -d ' ')
    • psจาก OSX พิมพ์หัวเรื่องเสมอดังนั้นSpeakusเสนอ:
      PGID="$( ps -o pgid "$PID" | grep [0-9] | tr -d ' ' )"
  • grep -o [0-9]* พิมพ์ตัวเลขต่อเนื่องเท่านั้น (ไม่พิมพ์ช่องว่างหรือส่วนหัวตามตัวอักษร)

บรรทัดคำสั่งเพิ่มเติม

PGID=$(ps -o pgid= $PID | grep -o [0-9]*)
kill -TERM -"$PGID"  # kill -15
kill -INT  -"$PGID"  # correspond to [CRTL+C] from keyboard
kill -QUIT -"$PGID"  # correspond to [CRTL+\] from keyboard
kill -CONT -"$PGID"  # restart a stopped process (above signals do not kill it)
sleep 2              # wait terminate process (more time if required)
kill -KILL -"$PGID"  # kill -9 if it does not intercept signals (or buggy)

การ จำกัด

  • เท่าที่สังเกตโดยdavideและHubert Karioเมื่อkillถูกเรียกใช้โดยกระบวนการที่เป็นของต้นไม้ต้นเดียวกันkillเสี่ยงต่อการฆ่าตัวตายก่อนที่จะยุติการฆ่าต้นไม้ทั้งหมด
  • ดังนั้นต้องแน่ใจว่าจะเรียกใช้คำสั่งโดยใช้กระบวนการมีที่แตกต่างกันในกระบวนการกลุ่ม-ID

เรื่องยาว

> cat run-many-processes.sh
#!/bin/sh
echo "ProcessID=$$ begins ($0)"
./child.sh background &
./child.sh foreground
echo "ProcessID=$$ ends ($0)"

> cat child.sh
#!/bin/sh
echo "ProcessID=$$ begins ($0)"
./grandchild.sh background &
./grandchild.sh foreground
echo "ProcessID=$$ ends ($0)"

> cat grandchild.sh
#!/bin/sh
echo "ProcessID=$$ begins ($0)"
sleep 9999
echo "ProcessID=$$ ends ($0)"

เรียกใช้แผนผังกระบวนการในพื้นหลังโดยใช้ '&'

> ./run-many-processes.sh &    
ProcessID=28957 begins (./run-many-processes.sh)
ProcessID=28959 begins (./child.sh)
ProcessID=28958 begins (./child.sh)
ProcessID=28960 begins (./grandchild.sh)
ProcessID=28961 begins (./grandchild.sh)
ProcessID=28962 begins (./grandchild.sh)
ProcessID=28963 begins (./grandchild.sh)

> PID=$!                    # get the Parent Process ID
> PGID=$(ps opgid= "$PID")  # get the Process Group ID

> ps fj
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
28348 28349 28349 28349 pts/3    28969 Ss   33021   0:00 -bash
28349 28957 28957 28349 pts/3    28969 S    33021   0:00  \_ /bin/sh ./run-many-processes.sh
28957 28958 28957 28349 pts/3    28969 S    33021   0:00  |   \_ /bin/sh ./child.sh background
28958 28961 28957 28349 pts/3    28969 S    33021   0:00  |   |   \_ /bin/sh ./grandchild.sh background
28961 28965 28957 28349 pts/3    28969 S    33021   0:00  |   |   |   \_ sleep 9999
28958 28963 28957 28349 pts/3    28969 S    33021   0:00  |   |   \_ /bin/sh ./grandchild.sh foreground
28963 28967 28957 28349 pts/3    28969 S    33021   0:00  |   |       \_ sleep 9999
28957 28959 28957 28349 pts/3    28969 S    33021   0:00  |   \_ /bin/sh ./child.sh foreground
28959 28960 28957 28349 pts/3    28969 S    33021   0:00  |       \_ /bin/sh ./grandchild.sh background
28960 28964 28957 28349 pts/3    28969 S    33021   0:00  |       |   \_ sleep 9999
28959 28962 28957 28349 pts/3    28969 S    33021   0:00  |       \_ /bin/sh ./grandchild.sh foreground
28962 28966 28957 28349 pts/3    28969 S    33021   0:00  |           \_ sleep 9999
28349 28969 28969 28349 pts/3    28969 R+   33021   0:00  \_ ps fj

คำสั่งpkill -P $PIDไม่ฆ่าหลาน:

> pkill -P "$PID"
./run-many-processes.sh: line 4: 28958 Terminated              ./child.sh background
./run-many-processes.sh: line 4: 28959 Terminated              ./child.sh foreground
ProcessID=28957 ends (./run-many-processes.sh)
[1]+  Done                    ./run-many-processes.sh

> ps fj
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
28348 28349 28349 28349 pts/3    28987 Ss   33021   0:00 -bash
28349 28987 28987 28349 pts/3    28987 R+   33021   0:00  \_ ps fj
    1 28963 28957 28349 pts/3    28987 S    33021   0:00 /bin/sh ./grandchild.sh foreground
28963 28967 28957 28349 pts/3    28987 S    33021   0:00  \_ sleep 9999
    1 28962 28957 28349 pts/3    28987 S    33021   0:00 /bin/sh ./grandchild.sh foreground
28962 28966 28957 28349 pts/3    28987 S    33021   0:00  \_ sleep 9999
    1 28961 28957 28349 pts/3    28987 S    33021   0:00 /bin/sh ./grandchild.sh background
28961 28965 28957 28349 pts/3    28987 S    33021   0:00  \_ sleep 9999
    1 28960 28957 28349 pts/3    28987 S    33021   0:00 /bin/sh ./grandchild.sh background
28960 28964 28957 28349 pts/3    28987 S    33021   0:00  \_ sleep 9999

คำสั่งkill -- -$PGIDฆ่ากระบวนการทั้งหมดรวมถึงหลาน

> kill --    -"$PGID"  # default signal is TERM (kill -15)
> kill -CONT -"$PGID"  # awake stopped processes
> kill -KILL -"$PGID"  # kill -9 to be sure

> ps fj
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
28348 28349 28349 28349 pts/3    29039 Ss   33021   0:00 -bash
28349 29039 29039 28349 pts/3    29039 R+   33021   0:00  \_ ps fj

ข้อสรุป

ฉันสังเกตเห็นในตัวอย่างนี้PIDและPGIDมีค่าเท่ากัน ( 28957)
นี่คือเหตุผลที่ฉันคิดว่าเดิมkill -- -$PIDเพียงพอ แต่ในกรณีที่กระบวนการจะวางไข่ภายในหมายเลขกระบวนการจะแตกต่างจากรหัสกลุ่มMakefile

ฉันคิดว่าkill -- -$(ps -o pgid= $PID | grep -o [0-9]*)เป็นเคล็ดลับง่ายๆที่ดีที่สุดในการฆ่าแผนผังกระบวนการทั้งหมดเมื่อถูกเรียกจากID กลุ่มอื่น (ต้นไม้กระบวนการอื่น)


1
สวัสดี @Davide คำถามที่ดี. ฉันคิดว่าkillควรส่งสัญญาณไปยังต้นไม้ทั้งหมดก่อนที่จะรับสัญญาณของมันเอง แต่ในบางสถานการณ์ / การใช้งานเฉพาะkillอาจส่งสัญญาณให้กับตัวเองถูกขัดจังหวะแล้วรับสัญญาณของตัวเอง อย่างไรก็ตามความเสี่ยงควรมีน้อยที่สุดและอาจถูกเพิกเฉยในกรณีส่วนใหญ่เนื่องจากข้อบกพร่องอื่น ๆ ควรเกิดขึ้นก่อนหน้านี้ ความเสี่ยงนี้สามารถถูกละเว้นในกรณีของคุณ? นอกจากนี้คำตอบอื่น ๆ ยังมีบั๊กทั่วไปนี้ ( killส่วนหนึ่งของแผนผังกระบวนการที่ถูกฆ่า) หวัง
ว่าความ

3
ใช้งานได้ก็ต่อเมื่อคำสั่งย่อยไม่กลายเป็นผู้นำกลุ่ม แม้แต่เครื่องมือง่ายๆอย่างเช่นmanทำเช่นนั้น ในทางตรงกันข้ามถ้าคุณต้องการฆ่ากระบวนการ gandchild จากกระบวนการลูกkill -- -$pidจะไม่ทำงาน ดังนั้นจึงไม่ใช่ทางออกทั่วไป
Hubert Kario

1
ตัวอย่างคือ "ลูก" พยายามที่จะฆ่าลูกของมัน (ดังนั้นลูกหลานของคำสั่งที่ผู้ใช้เริ่มต้น) IOW, child.shพยายามที่จะฆ่าลำดับชั้นของกระบวนการพื้นหลังใน
Hubert Kario

1
> kill -QUIT - "$ PGID" # สัญญาณเดียวกับ [CRTL + C] จากแป้นพิมพ์ ---- QUIT ควรถูกแทนที่เป็น INT ให้เป็นจริง
Maxim Kholyavkin

1
สำหรับตัวเลือก OSX - ไม่สนับสนุนส่วนหัวดังนั้นโค้ดควรได้รับการอัปเดตเป็น: PGID = "$ (ps -o pgid" $ PID "| grep [0-9] | tr -d '')"
Maxim Kholyavkin

166
pkill -TERM -P 27888

นี่จะฆ่ากระบวนการทั้งหมดที่มีกระบวนการหลัก ID 27888

หรือแข็งแกร่งกว่านี้:

CPIDS=$(pgrep -P 27888); (sleep 33 && kill -KILL $CPIDS &); kill -TERM $CPIDS

ซึ่งกำหนดการฆ่าใน 33 วินาทีต่อมาและขอให้กระบวนการยุติอย่างสุภาพ

ดูคำตอบนี้เพื่อยกเลิกลูกหลานทั้งหมด


19
ในการทดสอบอย่างรวดเร็วของฉัน pgrep รายงานเฉพาะเด็ก ๆ ทันทีดังนั้นสิ่งนี้อาจไม่ฆ่าลำดับชั้นทั้งหมด
haridsv

10
ฉันเห็นด้วยกับ @haridsv: pkill -Pส่งสัญญาณไปยังเด็กเท่านั้น => หลานไม่ได้รับสัญญาณ => ดังนั้นฉันจึงได้บิดคำตอบอื่นเพื่ออธิบายว่า Cheers ;-)
olibre

1
pkill -TERM -P ${$}จากสคริปต์ทุบตีเพื่อจะฆ่าลูกของตัวเองของคุณใช้
François Beausoleil

3
@Onlyjob การนอนหลับนั้นไม่เป็นอันตรายหรือไม่? ID กระบวนการอาจถูกนำกลับมาใช้ใหม่โดยระบบปฏิบัติการในระหว่างนี้: คุณอาจกำลังฆ่ากระบวนการที่ไม่ใช่ลูกของคุณอีกต่อไป ฉันสงสัยว่าการโทรไปที่ pkill จะต้องทำอีกครั้ง
François Beausoleil

2
@Onlyjob FYI ผมก็สามารถที่จะวางไข่ 32768 กระบวนการเกลียวเดียวกับแสง I / O การเข้าถึงในเวลาน้อยกว่า 19 วินาทีในเครื่องของฉัน:$ time for i in {1..32768}; do ( echo $BASHPID >> pids ); done real 0m18.860s
cychoi

102

เพื่อฆ่าต้นไม้กระบวนการซ้ำใช้ killtree ():

#!/bin/bash

killtree() {
    local _pid=$1
    local _sig=${2:--TERM}
    kill -stop ${_pid} # needed to stop quickly forking parent from producing children between child killing and parent killing
    for _child in $(ps -o pid --no-headers --ppid ${_pid}); do
        killtree ${_child} ${_sig}
    done
    kill -${_sig} ${_pid}
}

if [ $# -eq 0 -o $# -gt 2 ]; then
    echo "Usage: $(basename $0) <pid> [signal]"
    exit 1
fi

killtree $@

3
--ข้อโต้แย้งที่จะpsไม่ทำงานบน OS X ได้จะทำให้การทำงานมีแทนที่psคำสั่งโดย: ps ax -o "pid= ppid=" | grep -E "${_regex}" | sed -E "s/${_regex}/\1/gที่_regexถูกกำหนดไว้ก่อนที่จะforห่วง:local _regex="[ ]*([0-9]+)[ ]+${_pid}"
Artur

5
กระบวนการที่หยุดทำงานจะไม่ถูกฆ่าด้วย SIGTERM ดูคำตอบ
x-yuri

1
-1 ใช้ #! / bin / bash แทน #! / usr / bin / env bash (หรือดีกว่า POSIX สร้างและ / bin / sh เท่านั้น)
บุคคลที่ดี

มันจะเพียงพอหรือไม่ที่จะส่ง SIGKILL แทน SIGTERM เพื่อรับประกันว่าจะkilltree()ทำงานได้อย่างน่าเชื่อถือ?
davide

3
หากpsไม่รองรับ--ppidก็สามารถใช้pgrep -P ${_pid}แทนได้
Hubert Kario

16

คำสั่ง rkillจากแพ็คเกจ pslistส่งสัญญาณที่กำหนด (หรือSIGTERMโดยค่าเริ่มต้น) ไปยังกระบวนการที่ระบุและลูกหลานทั้งหมด:

rkill [-SIG] pid/name...

11

คำตอบของแบรดคือสิ่งที่ผมอยากแนะนำให้เกินไปยกเว้นว่าคุณสามารถทำไปด้วยawkโดยสิ้นเชิงถ้าคุณใช้ตัวเลือกในการ--ppidps

for child in $(ps -o pid -ax --ppid $PPID) do ....... done

สิ่งนี้ไม่ได้ผลสำหรับฉันเว้นแต่ฉันจะใช้ -ax ด้วยเหตุผลบางอย่าง (Centos5) มิฉะนั้นจะดีมาก!
xitrium

9

ถ้าคุณรู้ว่าผ่าน pid ของกระบวนการพาเรนต์นี่คือเชลล์สคริปต์ที่ควรใช้:

for child in $(ps -o pid,ppid -ax | \
   awk "{ if ( \$2 == $pid ) { print \$1 }}")
do
  echo "Killing child process $child because ppid = $pid"
  kill $child
done

ps บางเวอร์ชันจะส่งคำเตือนหากคุณใช้ "-ax" แทน "axe" ดังนั้น: สำหรับเด็กใน $ (ps -o pid, ppid ax | \ awk "{if (\ $ 2 == $ pid) {print \ $ 1}}")
Cory R. King

9

ฉันใช้วิธีการแก้ไขที่อธิบายไว้ที่นี่: https://stackoverflow.com/a/5311362/563175

ดังนั้นดูเหมือนว่า:

kill `pstree -p 24901 | sed 's/(/\n(/g' | grep '(' | sed 's/(\(.*\)).*/\1/' | tr "\n" " "`

ที่ 24901 เป็น PID ของผู้ปกครอง

มันดูน่าเกลียด แต่มันทำงานได้อย่างสมบูรณ์


1
ง่ายกับ grep แทน sed ...pstree -p 24901 | grep -oP '(?<=\()[0-9]+(?=\))'
anishsane

2
คุณควรเพิ่ม-lการpstreeเพื่อให้เส้นยาวไม่ได้รับการตัดทอน; มันสามารถทำให้อ่านง่ายขึ้นด้วยkill `pstree -l -p 24901 |grep "([[:digit:]]*)" -o |tr -d '()'`(ไม่จำเป็นต้องแปลง\nเป็นพื้นที่เพราะมันจะทำงานได้ดี), ขอบคุณ!
กุมภ์อำนาจ

9

คำตอบของ zhigang รุ่นที่แก้ไขแล้ว:

#!/usr/bin/env bash
set -eu

killtree() {
    local pid
    for pid; do
        kill -stop $pid
        local cpid
        for cpid in $(pgrep -P $pid); do
            killtree $cpid
        done
        kill $pid
        kill -cont $pid
        wait $pid 2>/dev/null || true
   done
}

cpids() {
    local pid=$1 options=${2:-} space=${3:-}
    local cpid
    for cpid in $(pgrep -P $pid); do
        echo "$space$cpid"
        if [[ "${options/a/}" != "$options" ]]; then
            cpids $cpid "$options" "$space  "
        fi
    done
}

while true; do sleep 1; done &
cpid=$!
for i in $(seq 1 2); do
    cpids $$ a
    sleep 1
done
killtree $cpid
echo ---
cpids $$ a

คุณสามารถทำได้wait $pidเฉพาะกระบวนการที่คุณเริ่มต้นเท่านั้นไม่ใช่กระบวนการก่อนทั้งหมดดังนั้นนี่ไม่ใช่วิธีแก้ปัญหาทั่วไป
Hubert Kario

@Hubert Kario ในกรณีนี้การรอจะออกจากสถานะที่ไม่เป็นศูนย์และดำเนินการสคริปต์ต่อไป ฉันผิดหรือเปล่า? แต่waitจะระงับTerminatedข้อความถ้าเป็นเด็ก
x-yuri

9

ฉันไม่สามารถแสดงความคิดเห็น (มีชื่อเสียงไม่เพียงพอ) ดังนั้นฉันถูกบังคับให้เพิ่มคำตอบใหม่แม้ว่านี่จะไม่ใช่คำตอบจริงๆ

มีปัญหาเล็กน้อยกับคำตอบที่ดีมากและละเอียดโดย @olibre เมื่อวันที่ 28 ก.พ. ผลลัพธ์ของps opgid= $PIDจะมีช่องว่างนำหน้าสำหรับ PID ที่สั้นกว่าห้าหลักเพราะpsจัดชิดคอลัมน์ (จัดตำแหน่งตัวเลขให้ตรง) ภายในบรรทัดคำสั่งทั้งหมดผลลัพธ์จะมีเครื่องหมายลบตามด้วยช่องว่างตามด้วยกลุ่ม PID วิธีง่ายๆคือการท่อpsเพื่อtrไปที่ช่องว่างลบ:

kill -- -$( ps opgid= $PID | tr -d ' ' )

ขอบคุณ tanager ฉันจะแก้ไขคำตอบของฉัน;) ไชโย
olibre

8

เพื่อเพิ่มคำตอบของนอร์แมนแรมซีย์มันอาจคุ้มค่าที่จะดู setsid ถ้าคุณต้องการสร้างกลุ่มกระบวนการ
http://pubs.opengroup.org/onlinepubs/009695399/functions/setsid.html

ฟังก์ชัน setsid () จะสร้างเซสชันใหม่หากกระบวนการเรียกใช้ไม่ใช่หัวหน้ากลุ่มกระบวนการ เมื่อกลับกระบวนการเรียกจะเป็นผู้นำเซสชันของเซสชั่นใหม่นี้จะต้องเป็นหัวหน้ากลุ่มกระบวนการของกลุ่มกระบวนการใหม่และจะต้องไม่มีสถานีควบคุม รหัสกลุ่มกระบวนการของกระบวนการที่เรียกจะต้องตั้งค่าเท่ากับ ID กระบวนการของกระบวนการเรียก กระบวนการเรียกใช้จะเป็นกระบวนการเดียวในกลุ่มกระบวนการใหม่และกระบวนการเดียวในเซสชันใหม่

ซึ่งฉันหมายความว่าคุณสามารถสร้างกลุ่มจากกระบวนการเริ่มต้น ฉันใช้สิ่งนี้ใน php เพื่อที่จะสามารถฆ่าต้นไม้กระบวนการทั้งหมดหลังจากเริ่มต้นมัน

นี่อาจเป็นความคิดที่ไม่ดี ฉันจะสนใจในความคิดเห็น


จริงๆแล้วนี่เป็นความคิดที่ดีและทำงานได้ดีมาก ฉันใช้มันในกรณีที่ฉันสามารถวางกระบวนการในกลุ่มกระบวนการเดียวกัน (หรือพวกเขาทั้งหมดพร้อมในกลุ่มเดียวกัน)
sickill

7

แรงบันดาลใจจากความคิดเห็นของ ysth

kill -- -PGID

แทนที่จะให้มันเป็นหมายเลขกระบวนการให้มันปฏิเสธของหมายเลขกลุ่ม ตามปกติด้วยคำสั่งเกือบทุกชนิดหากคุณต้องการอาร์กิวเมนต์ปกติที่ขึ้นต้นด้วย a -ไม่สามารถตีความได้ว่าเป็นสวิตช์ให้นำหน้าด้วย--


โอ๊ะฉันเพิ่งรู้ว่าฉันได้รับคำตอบเดียวกับคุณ => +1 แต่ยิ่งไปกว่านั้นฉันอธิบายวิธีที่จะได้รับPGIDจากPIDจากคุณคิดอย่างไร? ไชโย
โอลิเบอร์

5

ฟังก์ชั่นเชลล์ต่อไปนี้คล้ายกับคำตอบอื่น ๆ มากมาย แต่สามารถใช้งานได้ทั้งบน Linux และ BSD (OS X และอื่น ๆ ) โดยไม่ต้องพึ่งพาภายนอกเช่นpgrep:

killtree() {
    local parent=$1 child
    for child in $(ps -o ppid= -o pid= | awk "\$1==$parent {print \$2}"); do
        killtree $child
    done
    kill $parent
}

ฉันคิดว่าคุณมีคำว่า "เด็ก" เป็นพิเศษในตอนท้ายของบรรทัดที่ 2
mato

@mato - นั่นไม่ใช่เรื่องพิเศษ มัน จำกัด ขอบเขตของ$childฟังก์ชั่นนั้นเพื่อที่จะไม่รบกวนตัวแปรอื่น ๆ (ที่ไม่ใช่ในท้องถิ่น) ด้วยชื่อเดียวกันและเพื่อให้แน่ใจว่าค่าของตัวแปรท้องถิ่นจะถูกล้างออกหลังจากฟังก์ชั่นสิ้นสุดลง
Adam Katz

โอ้ฉันเห็นแล้วฉันคิดว่ามันเป็นส่วนหนึ่งของงานที่มอบหมาย ฉันเดาว่าฉันควรอ่านใหม่ (ช้ากว่า) ก่อนที่จะเขียนความคิดเห็น :) ขอบคุณ
mato

Btw มันจะดีกว่าไหมถ้าจะสร้างรายชื่อเด็ก ๆ แล้วเริ่มฆ่าจากด้านบน (พ่อแม่)? .. ดังนั้นเราสามารถหลีกเลี่ยงสถานการณ์เมื่อผู้ปกครองสร้างเด็กหรือยังคงใช้รหัสถัดไปซึ่งอาจเปลี่ยนแปลงพฤติกรรมที่ตั้งใจ
mato

5

มันง่ายสุดในการทำเช่นนี้กับงูหลามใช้psutil เพียงติดตั้ง psutil ด้วย pip แล้วคุณมีชุดเครื่องมือการจัดการกระบวนการที่สมบูรณ์แบบ:

def killChildren(pid):
    parent = psutil.Process(pid)
    for child in parent.get_children(True):
        if child.is_running():
            child.terminate()

5

ตามคำตอบของ zhigang สิ่งนี้หลีกเลี่ยงการฆ่าตัวตาย:

init_killtree() {
    local pid=$1 child

    for child in $(pgrep -P $pid); do
        init_killtree $child
    done
    [ $pid -ne $$ ] && kill -kill $pid
}

4

หากคุณต้องการฆ่ากระบวนการตามชื่อ:

killall -9 -g someprocessname

หรือ

pgrep someprocessname | xargs pkill -9 -g

3

นี่เป็นเวอร์ชั่นของฉันที่จะฆ่ากระบวนการลูกทั้งหมดโดยใช้ bash script มันไม่ได้ใช้การสอบถามซ้ำและขึ้นอยู่กับคำสั่ง pgrep

ใช้

killtree.sh PID SIGNAL

เนื้อหาของ killtrees.sh

#!/bin/bash
PID=$1
if [ -z $PID ];
then
    echo "No pid specified"
fi

PPLIST=$PID
CHILD_LIST=`pgrep -P $PPLIST -d,`

while [ ! -z "$CHILD_LIST" ]
do
    PPLIST="$PPLIST,$CHILD_LIST"
    CHILD_LIST=`pgrep -P $CHILD_LIST -d,`
done

SIGNAL=$2

if [ -z $SIGNAL ]
then
    SIGNAL="TERM"
fi
#do substring from comma to space
kill -$SIGNAL ${PPLIST//,/ }

1
สิ่งนี้จะล้มเหลวหากมีการสร้างกระบวนการใหม่หลังจากสร้างรายการลูก
ceving

3

นี่คือความแตกต่างของคำตอบของ @ zhigang ซึ่งทำโดยไม่มี AWK อาศัยความเป็นไปได้ในการแยกวิเคราะห์ของ Bash เท่านั้น:

function killtree {
  kill -STOP "$1"
  ps -e -o pid= -o ppid= | while read -r pid ppid
                           do
                             [[ $ppid = $1 ]] || continue
                             killtree "$pid"  || true # Skip over failures
                           done
  kill -CONT "$1"          
  kill -TERM "$1"
}

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


1

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

# kill my group's subprocesses:          killGroup
# kill also myself:                      killGroup -x
# kill another group's subprocesses:     killGroup N  
# kill that group all:                   killGroup -x N
# N: PID of the main process (= process group ID).

function killGroup () {
    local prid mainpid
    case $1 in
        -x) [ -n "$2" ] && kill -9 -$2 || kill -9 -$$ ;;
        "") mainpid=$$ ;;
         *) mainpid=$1 ;;
    esac
    prid=$(ps ax -o pid,pgid | grep $mainpid)
    prid=${prid//$mainpid/}
    kill -9 $prid 2>/dev/null
    return
}

ไชโย



1

น่าจะเป็นการดีกว่าที่จะฆ่าพ่อแม่ก่อนลูก มิฉะนั้นผู้ปกครองอาจวางไข่เด็กใหม่อีกครั้งก่อนที่เขาจะฆ่าตัวตาย สิ่งเหล่านี้จะรอดชีวิตจากการฆ่า

รุ่น ps ของฉันแตกต่างจากข้างบน อาจจะแก่เกินไปดังนั้น grepping แปลก ...

วิธีใช้เชลล์สคริปต์แทนฟังก์ชันเชลล์มีข้อดีหลายประการ ...

อย่างไรก็ตามมันเป็นความคิดพื้นฐานของ zhigang


#!/bin/bash
if test $# -lt 1 ; then
    echo >&2 "usage: kiltree pid (sig)"
fi ;

_pid=$1
_sig=${2:-TERM}
_children=$(ps j | grep "^[ ]*${_pid} " | cut -c 7-11) ;
echo >&2 kill -${_sig} ${_pid}
kill -${_sig} ${_pid}
for _child in ${_children}; do
    killtree ${_child} ${_sig}
done

โปรดทราบว่าสคริปต์ @zhighang SIGSTOPs กระบวนการหลักและส่งสัญญาณไปยังกระบวนการที่หยุดดังนั้นสิ่งนี้ไม่ควร (AFAIK) ทำให้เกิดสภาพการแย่งชิงระหว่างกระบวนการสร้างลูกและการส่งสัญญาณ แม้ว่าเวอร์ชั่นของคุณจะมีการแข่งขันระหว่างการรับรายชื่อลูกและการส่งสัญญาณไปยังผู้ปกครอง
Hubert Kario

1

สิ่งต่อไปนี้ได้รับการทดสอบบน FreeBSD, Linux และ MacOS X และขึ้นอยู่กับ pgrep และ kill เท่านั้น (เวอร์ชัน ps -o ไม่ทำงานภายใต้ BSD) อาร์กิวเมนต์แรกคือ parent pid ที่ลูกต้องถูกยกเลิก อาร์กิวเมนต์ที่สองคือบูลีนเพื่อพิจารณาว่า pid พาเรนต์ต้องถูกยกเลิกเช่นกัน

KillChilds() {
        local pid="${1}"
        local self="${2:-false}"

        if children="$(pgrep -P "$pid")"; then
                for child in $children; do
                        KillChilds "$child" true
                done
        fi

        if [ "$self" == true ]; then
                kill -s SIGTERM "$pid" || (sleep 10 && kill -9 "$pid" &)
        fi
}

KillChilds $$ > /dev/null 2>&1

สิ่งนี้จะส่ง SIGTERM ไปยังกระบวนการ child / grandchild ใด ๆ ภายในเชลล์สคริปต์และหาก SIGTERM ไม่สำเร็จมันจะรอ 10 วินาทีจากนั้นส่ง kill


คำตอบก่อนหน้านี้:

ต่อไปนี้ใช้งานได้ แต่จะฆ่าเชลล์ตัวเองใน BSD

KillSubTree() {
    local parent="${1}"
    for child in $(ps -o pid=$parent); do
            if [ $$ -ne $child ]; then (kill -s SIGTERM $child || (sleep 10 && kill -9 $child & )) > /dev/null 2>&1 ; fi
    done
}
# Example lanch from within script
KillSubTree $$ > /dev/null 2>&1

1

ฉันพัฒนาวิธีแก้ปัญหาของ zhigang, xyuri และ solidsneck เพิ่มเติม:

 #!/bin/bash

if test $# -lt 1 ; then
    echo >&2 "usage: kiltree pid (sig)"
    exit 1 ;
  fi ;

_pid=$1
_sig=${2:-TERM}

# echo >&2 "killtree($_pid) mypid = $$"
# ps axwwf | grep -6 "^[ ]*$_pid " >&2 ;

function _killtree () {
    local _children
    local _child
    local _success

    if test $1 -eq $2 ; then # this is killtree - don't commit suicide!
        echo >&2 "killtree can´t kill it´s own branch - some processes will survive." ; 
        return 1 ;
      fi ;
    # this avoids that children are spawned or disappear.
    kill -SIGSTOP $2 ;

    _children=$(ps -o pid --no-headers --ppid $2) ;        
    _success=0 
    for _child in ${_children}; do
        _killtree $1 ${_child} $3 ;
        _success=$(($_success+$?)) ;
      done ;

    if test $_success -eq 0 ; then
        kill -$3 $2
      fi ;
    # when a stopped process is killed, it will linger in the system until it is continued
    kill -SIGCONT $2
    test $_success -eq 0 ;
    return $?
    }

_killtree $$ $_pid $_sig

รุ่นนี้จะหลีกเลี่ยงการฆ่าบรรพบุรุษ - ซึ่งเป็นสาเหตุให้เกิดกระบวนการเด็กในการแก้ไขปัญหาก่อนหน้านี้

กระบวนการหยุดทำงานอย่างถูกต้องก่อนที่จะพิจารณารายการย่อยดังนั้นจึงไม่มีการสร้างลูกใหม่หรือหายไป

หลังจากถูกฆ่างานที่หยุดจะต้องหายไปจากระบบต่อไป


1

คำถามเก่าฉันรู้ แต่คำตอบทั้งหมดดูเหมือนจะเรียก ps ซึ่งฉันไม่ชอบ

โซลูชันที่ใช้ awk นี้ไม่ต้องการการเรียกซ้ำและเรียกใช้ ps เพียงครั้งเดียว

awk 'BEGIN {
  p=1390
  while ("ps -o ppid,pid"|getline) a[$1]=a[$1]" "$2
  o=1
  while (o==1) {
    o=0
    split(p, q, " ")
    for (i in q) if (a[q[i]]!="") {
      p=p""a[q[i]]
      o=1
      a[q[i]]=""
    }
  }
  system("kill -TERM "p)
}'

หรือบนบรรทัดเดียว:

awk 'BEGIN {p=1390;while ("ps -o ppid,pid"|getline) a[$1]=a[$1]" "$2;o=1;while (o==1) {o=0;split(p, q, " ");for (i in q) {if (a[q[i]]!="") {p=p""a[q[i]];o=1;a[q[i]]=""}}}system("kill -TERM "p)}'

โดยพื้นฐานแล้วความคิดคือเราสร้างอาร์เรย์ (a) ของรายการ parent: รายการลูกจากนั้นวนรอบอาร์เรย์หาเด็ก ๆ สำหรับผู้ปกครองที่ตรงกันของเราเพิ่มเข้าไปในรายการผู้ปกครองของเรา (p)

หากคุณไม่ต้องการฆ่ากระบวนการระดับบนสุดให้ทำ

sub(/[0-9]*/, "", p)

ก่อนที่บรรทัด system () จะลบออกจากชุดคิท

โปรดจำไว้ว่ามีสภาพการแข่งขันที่นี่ แต่จริง (เท่าที่ฉันเห็น) ของการแก้ปัญหาทั้งหมด มันทำในสิ่งที่ฉันต้องการเพราะสคริปต์ที่ฉันต้องการไม่ใช่การสร้างเด็กอายุสั้นจำนวนมาก

แบบฝึกหัดสำหรับผู้อ่านจะทำให้เป็นวง 2 รอบ: หลังจากผ่านครั้งแรกส่ง SIGSTOP ไปยังกระบวนการทั้งหมดในรายการ p จากนั้นวนรอบเพื่อเรียกใช้ ps อีกครั้งและหลังจากผ่านครั้งที่สองส่ง SIGTERM จากนั้น SIGCONT หากคุณไม่สนเรื่องตอนจบที่ดีถ้างั้นรหัสผ่านที่สองก็น่าจะ SIGKILL ฉันคิดว่า


0

หากคุณรู้ว่า pid ของสิ่งที่คุณต้องการฆ่าคุณสามารถไปจาก session id และทุกอย่างใน session เดียวกัน ฉันจะตรวจสอบอีกครั้ง แต่ฉันใช้สิ่งนี้สำหรับสคริปต์ที่เริ่ม rsyncs ในลูปที่ฉันต้องการตายและไม่เริ่มต้นใหม่ (เนื่องจากการวนซ้ำ) ตามที่ต้องการหากฉันเพิ่งจะ rsall ฆ่าทั้งหมด

kill $(ps -o pid= -s $(ps -o sess --no-heading --pid 21709))

หากคุณไม่ทราบว่า pid คุณยังสามารถซ้อนได้มากขึ้น

kill $(ps -o pid= -s $(ps -o sess --no-heading --pid $(pgrep rsync )))


0

ใน sh คำสั่ง jobs จะแสดงรายการกระบวนการพื้นหลัง ในบางกรณีอาจเป็นการดีกว่าที่จะฆ่ากระบวนการใหม่ก่อนเช่นที่เก่ากว่าสร้างซ็อกเก็ตที่ใช้ร่วมกัน ในกรณีเหล่านี้เรียงลำดับ PID ในลำดับย้อนกลับ บางครั้งคุณต้องการรอสักครู่เพื่อให้งานเขียนบางอย่างลงบนดิสก์หรืออะไรแบบนั้นก่อนที่จะหยุด

และอย่าฆ่าถ้าคุณไม่ต้องทำ!

for SIGNAL in TERM KILL; do
  for CHILD in $(jobs -s|sort -r); do
    kill -s $SIGNAL $CHILD
    sleep $MOMENT
  done
done

0

ฆ่ากระบวนการลูกในเชลล์สคริปต์:

หลายครั้งที่เราต้องฆ่ากระบวนการเด็กที่ถูกแขวนคอหรือปิดกั้นด้วยเหตุผลบางอย่าง เช่น. ปัญหาการเชื่อมต่อ FTP

มีสองวิธีคือ

1) เพื่อสร้างผู้ปกครองใหม่แยกต่างหากสำหรับเด็กแต่ละคนซึ่งจะตรวจสอบและฆ่ากระบวนการเด็กเมื่อหมดเวลาถึง

สร้าง test.sh ดังนี้

#!/bin/bash

declare -a CMDs=("AAA" "BBB" "CCC" "DDD")
for CMD in ${CMDs[*]}; do
    (sleep 10 & PID=$!; echo "Started $CMD => $PID"; sleep 5; echo "Killing $CMD => $PID"; kill $PID; echo "$CMD Completed.") &
done
exit;

และดูกระบวนการที่มีชื่อเป็น 'ทดสอบ' ในสถานีอื่นโดยใช้คำสั่งดังต่อไปนี้

watch -n1 'ps x -o "%p %r %c" | grep "test" '

สคริปต์ด้านบนจะสร้างกระบวนการลูกใหม่ 4 กระบวนการและผู้ปกครอง กระบวนการลูกแต่ละกระบวนการจะทำงานเป็นเวลา 10 วินาที แต่เมื่อหมดเวลาของการเข้าถึง 5 วินาทีกระบวนการพ่อแม่ของพวกเขาจะฆ่าเด็กเหล่านั้น ดังนั้นเด็กจะไม่สามารถดำเนินการให้เสร็จสมบูรณ์ได้ (10 วินาที) เล่นรอบเวลาเหล่านั้น (สลับ 10 และ 5) เพื่อดูพฤติกรรมอื่น ในกรณีที่เด็กจะเสร็จสิ้นการดำเนินการใน 5 วินาทีก่อนที่จะหมดเวลา 10 วินาที

2) ให้ผู้ปกครองปัจจุบันตรวจสอบและฆ่ากระบวนการเด็กเมื่อหมดเวลา สิ่งนี้จะไม่สร้างพาเรนต์แยกต่างหากเพื่อตรวจสอบเด็กแต่ละคน นอกจากนี้คุณสามารถจัดการกระบวนการลูกทั้งหมดอย่างถูกต้องภายในผู้ปกครองเดียวกัน

สร้าง test.sh ดังนี้

#!/bin/bash

declare -A CPIDs;
declare -a CMDs=("AAA" "BBB" "CCC" "DDD")

CMD_TIME=15;
for CMD in ${CMDs[*]}; do
    (echo "Started..$CMD"; sleep $CMD_TIME; echo "$CMD Done";) &
    CPIDs[$!]="$RN";
    sleep 1;
done

GPID=$(ps -o pgid= $$);
CNT_TIME_OUT=10;
CNT=0;
while (true); do
    declare -A TMP_CPIDs;

    for PID in "${!CPIDs[@]}"; do
        echo "Checking "${CPIDs[$PID]}"=>"$PID;

        if ps -p $PID > /dev/null ; then
          echo "-->"${CPIDs[$PID]}"=>"$PID" is running..";
          TMP_CPIDs[$PID]=${CPIDs[$PID]};
        else
          echo "-->"${CPIDs[$PID]}"=>"$PID" is completed.";
        fi
    done

    if [ ${#TMP_CPIDs[@]} == 0 ]; then
        echo "All commands completed.";
        break;
    else
        unset CPIDs;
        declare -A CPIDs;
        for PID in "${!TMP_CPIDs[@]}"; do
            CPIDs[$PID]=${TMP_CPIDs[$PID]};
        done
        unset TMP_CPIDs;

        if [ $CNT -gt $CNT_TIME_OUT ]; then
            echo ${CPIDs[@]}"PIDs not reponding. Timeout reached $CNT sec. killing all childern with GPID $GPID..";
            kill -- -$GPID;
        fi
    fi

    CNT=$((CNT+1));
    echo "waiting since $b secs..";
    sleep 1;
done

exit;

และดูกระบวนการที่มีชื่อเป็น 'ทดสอบ' ในสถานีอื่นโดยใช้คำสั่งดังต่อไปนี้

watch -n1 'ps x -o "%p %r %c" | grep "test" '

สคริปต์ด้านบนจะสร้างกระบวนการลูกใหม่ 4 กระบวนการ เรากำลังจัดเก็บ pids ของกระบวนการลูกทั้งหมดและวนรอบพวกเขาเพื่อตรวจสอบว่าพวกเขาจะเสร็จสิ้นการดำเนินการของพวกเขาหรือยังคงทำงานอยู่ กระบวนการลูกจะดำเนินการจนถึงเวลา CMD_TIME แต่ถ้า CNT_TIME_OUT หมดเวลาเข้าถึงเด็กทุกคนจะถูกฆ่าโดยกระบวนการหลัก คุณสามารถสลับเวลาและเล่นกับสคริปต์เพื่อดูพฤติกรรม ข้อเสียเปรียบประการหนึ่งของวิธีนี้คือใช้รหัสประจำกลุ่มเพื่อฆ่าต้นไม้ลูกทั้งหมด แต่กระบวนการผู้ปกครองเองก็อยู่ในกลุ่มเดียวกันดังนั้นมันจึงถูกฆ่าเช่นกัน

คุณอาจต้องกำหนดรหัสกลุ่มอื่นให้กับกระบวนการผู้ปกครองหากคุณไม่ต้องการให้ผู้ปกครองถูกฆ่า

รายละเอียดเพิ่มเติมสามารถดูได้ที่นี่

ฆ่ากระบวนการลูกในเชลล์สคริปต์


0

สคริปต์นี้ยังใช้งานได้:

#/bin/sh while true do echo "Enter parent process id [type quit for exit]" read ppid if [ $ppid -eq "quit" -o $ppid -eq "QUIT" ];then exit 0 fi for i in `ps -ef| awk '$3 == '$ppid' { print $2 }'` do echo killing $i kill $i done done


0

ฉันรู้ว่ามันเก่า แต่นั่นเป็นทางออกที่ดีกว่าที่ฉันพบ:

killtree() { 
    for p in $(pstree -p $1 | grep -o "([[:digit:]]*)" |grep -o "[[:digit:]]*" | tac);do
        echo Terminating: $p 
        kill $p
    done
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.