Cách tốt nhất để gửi tín hiệu cho tất cả các thành viên của một nhóm quy trình là gì?


422

Tôi muốn giết cả một cây quá trình. Cách tốt nhất để làm điều này bằng cách sử dụng bất kỳ ngôn ngữ kịch bản thông thường là gì? Tôi đang tìm kiếm một giải pháp đơn giản.


3
Zombie sẽ biến mất khi máy gặt hệ thống chạy. Tôi sẽ thừa nhận rằng tôi đã thấy các hệ thống nơi zombie nán lại, nhưng điều đó không điển hình.
Brian Knoblauch

7
Đôi khi những thây ma còn sót lại chịu trách nhiệm cho một số hoạt động đáng sợ.
dùng1

Sử dụng một trong các lệnh chronoshoặc herodes.
Michael Le Barbier Grünewald

8
giết $ (pstree <PID> -p -a -l | cắt -d, -f2 | cắt -d'' -f1)
vrdhn

@ MichaelLeBarbierGrünewald Bạn có thể vui lòng liên kết đến các chương trình đó không?
xốp bay

Câu trả lời:


305

Bạn không nói nếu cây bạn muốn giết là một nhóm quy trình duy nhất. (Đây thường là trường hợp nếu cây là kết quả của việc bắt đầu từ máy chủ bắt đầu hoặc dòng lệnh shell.) Bạn có thể khám phá các nhóm quy trình bằng GNU ps như sau:

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

Nếu đó là một nhóm quy trình bạn muốn giết, chỉ cần sử dụng kill(1)lệnh nhưng thay vì cho nó một số quy trình, hãy cho nó phủ định số nhóm. Ví dụ để tiêu diệt mọi quy trình trong nhóm 5112, hãy sử dụng kill -TERM -- -5112.


3
kill -74313 -bash: kill: 74313: đặc tả tín hiệu không hợp lệ Nếu tôi thêm lệnh giết -15 -GPID, nó hoạt động hoàn hảo.
Adam Peck

51
Như thường lệ với hầu hết mọi lệnh, nếu bạn muốn một đối số bình thường bắt đầu bằng một - không được hiểu là một công tắc, hãy đặt trước nó bằng -: kill - -GPID
ysth

9
pgrepcó thể cung cấp một cách dễ dàng hơn để tìm ID nhóm quy trình. Ví dụ: để giết nhóm quy trình của my-script.sh, hãy chạy kill -TERM -$(pgrep -o my-script.sh).
Josh Kelley

9
Xem tốt hơn về stackoverflow.com/questions/392022/ cho đến nay là một giải pháp thanh lịch hơn và nếu bạn cần liệt kê các pids của trẻ em thì hãy sử dụng:ps -o pid --no-headers --ppid $PARENT_PID
Szymon Jeż

4
Và nếu bạn sửa đổi định dạng một chút và sắp xếp, bạn sẽ thấy tất cả các quy trình được nhóm độc đáo và bắt đầu với (có khả năng) cha mẹ nhóm trong mỗi nhóm:ps x -o "%r %p %y %x %c" | sort -nk1,2
haridsv

199

Giết tất cả các quy trình thuộc cùng một cây quy trình bằng cách sử dụng ID nhóm quy trình ( PGID)

  • kill -- -$PGID     Sử dụng tín hiệu mặc định ( TERM= 15)
  • kill -9 -$PGID     Sử dụng tín hiệu KILL(9)

Bạn có thể truy xuất PGIDtừ bất kỳ ID quy trình ( PID) nào của cùng một cây quy trình

  • kill -- -$(ps -o pgid= $PID | grep -o '[0-9]*')   (tín hiệu TERM)
  • kill -9 -$(ps -o pgid= $PID | grep -o '[0-9]*')   (tín hiệu KILL)

Đặc biệt cảm ơn tanagerspeakus vì những đóng góp trên $PIDcác không gian còn lại và khả năng tương thích OSX.

Giải trình

  • kill -9 -"$PGID"=> Gửi tín hiệu 9 ( KILL) cho tất cả con và cháu ...
  • PGID=$(ps opgid= "$PID")=> Truy xuất ID nhóm quy trình từ bất kỳ ID quy trình nào của cây, không chỉ ID quy trình-cha mẹ . Một biến thể của ps opgid= $PIDps -o pgid --no-headers $PIDnơi pgidcó thể được thay thế bởi pgrp.
    Nhưng:
    • pschèn các khoảng trắng hàng đầu khi PIDnhỏ hơn năm chữ số và được căn phải theo thông báo của tanager . Bạn có thể dùng:
      PGID=$(ps opgid= "$PID" | tr -d ' ')
    • pstừ OSX luôn in tiêu đề, do đó, speakus đề xuất:
      PGID="$( ps -o pgid "$PID" | grep [0-9] | tr -d ' ' )"
  • grep -o [0-9]* chỉ in các chữ số liên tiếp (không in dấu cách hoặc tiêu đề bảng chữ cái).

Dòng lệnh tiếp theo

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)

Giới hạn

  • Như được chú ý bởi davideHubert Kario , khi killđược gọi bởi một quá trình thuộc cùng một cây, có killnguy cơ tự sát trước khi chấm dứt toàn bộ việc giết cây.
  • Do đó, hãy chắc chắn chạy lệnh bằng cách sử dụng một quy trình có ID nhóm quy trình khác nhau .

Câu chuyện dài

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

Chạy cây quy trình trong nền bằng cách sử dụng '&'

> ./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

Lệnh pkill -P $PIDkhông giết cháu:

> 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

Lệnh kill -- -$PGIDgiết tất cả các quá trình bao gồm cả cháu.

> 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

Phần kết luận

Tôi nhận thấy trong ví dụ này PIDPGIDbằng ( 28957).
Đây là lý do tại sao tôi nghĩ ban đầu kill -- -$PIDlà đủ. Nhưng trong trường hợp quá trình này được sinh ra trong một Makefilesự Process ID khác với Group ID .

Tôi nghĩ kill -- -$(ps -o pgid= $PID | grep -o [0-9]*)là thủ thuật đơn giản tốt nhất để tiêu diệt toàn bộ cây quy trình khi được gọi từ ID nhóm khác (cây quy trình khác).


1
Xin chào @davide. Câu hỏi hay. Tôi nghĩ killnên luôn gửi tín hiệu đến toàn bộ cây trước khi nhận tín hiệu riêng. Nhưng trong một số trường hợp / việc triển khai cụ thể, killcó thể gửi cho chính nó tín hiệu, bị gián đoạn và sau đó nhận tín hiệu của chính nó. Tuy nhiên, rủi ro phải đủ tối thiểu và có thể bị bỏ qua trong hầu hết các trường hợp vì các lỗi khác sẽ xảy ra trước lỗi này. Nguy cơ này có thể được bỏ qua trong trường hợp của bạn? Hơn nữa, các câu trả lời khác có lỗi phổ biến này ( killmột phần của cây quá trình bị giết). Hy vọng sự giúp đỡ này .. Chúc mừng;)
olibre

3
Điều này chỉ hoạt động nếu bản thân các lệnh phụ không trở thành trưởng nhóm. Ngay cả những công cụ đơn giản như thế mancũng làm được điều đó. Mặt khác, nếu bạn muốn giết quá trình gandchild từ quá trình con, kill -- -$pidsẽ không hoạt động. Vì vậy, nó không phải là giải pháp chung chung.
Hubert Kario

1
Ví dụ là "đứa trẻ" đang cố giết con của nó (vì vậy cháu của người dùng đã ra lệnh). IOW, cố gắng giết hệ thống phân cấp quá trình nền trong child.sh.
Hubert Kario

1
> kill -QUIT - "$ PGID" # tín hiệu tương tự như [CRTL + C] từ bàn phím ---- QUIT nên được thay thế thành INT là đúng
Maxim Kholyavkin

1
đối với tùy chọn OSX - không có tiêu đề không được hỗ trợ nên mã phải được cập nhật thành: PGID = "$ (ps -o pgid" $ PID "| grep [0-9] | tr -d '')"
Maxim Kholyavkin

166
pkill -TERM -P 27888

Điều này sẽ giết tất cả các quy trình có ID process 27888.

Hoặc mạnh mẽ hơn:

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

mà lịch trình giết chết 33 giây sau đó và lịch sự yêu cầu các quá trình chấm dứt.

Xem câu trả lời này để chấm dứt tất cả con cháu.


19
Trong thử nghiệm nhanh của tôi, pgrep chỉ báo cáo những đứa trẻ ngay lập tức, vì vậy điều này có thể không giết chết toàn bộ hệ thống phân cấp.
haridsv

10
Tôi đồng ý với @haridsv: chỉ pkill -Pgửi tín hiệu cho trẻ => cháu không nhận được tín hiệu => Vì vậy, tôi đã viết một câu trả lời khác để giải thích điều đó. Chúc mừng ;-)
olibre

1
Từ một kịch bản bash, để giết con của bạn, sử dụng pkill -TERM -P ${$}.
François Beausoleil

3
@Onlyjob, ngủ có nguy hiểm không? Trong khi đó, ID quy trình có thể đã được HĐH sử dụng lại: bạn có thể sẽ giết các quy trình không còn là con bạn nữa. Tôi nghi ngờ cuộc gọi tới pkill sẽ phải được thực hiện lại để đảm bảo chống lại điều này.
François Beausoleil

2
@Onlyjob FYI, tôi có thể đẻ trứng 32768 quy trình, luồng duy nhất, với tôi ánh sáng / O truy cập trong vòng chưa đầy 19 giây trên máy tính của tôi:$ time for i in {1..32768}; do ( echo $BASHPID >> pids ); done real 0m18.860s
cychoi

102

Để giết cây quy trình một cách đệ quy, hãy sử dụng 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
Các --đối số pskhông hoạt động trên OS X. Để làm cho nó hoạt động, hãy thay thế pslệnh bằng cách: ps ax -o "pid= ppid=" | grep -E "${_regex}" | sed -E "s/${_regex}/\1/gwhere _regexđược xác định trước forvòng lặp:local _regex="[ ]*([0-9]+)[ ]+${_pid}"
artur

5
Các quy trình đã dừng không bị giết bằng SIGTERM. Xem câu trả lời
x-yuri

1
-1 sử dụng #! / Bin / bash thay vì #! / Usr / bin / env bash (hoặc tốt hơn là POSIX chỉ xây dựng và / bin / sh)
Người tốt

Nó có đủ để gửi SIGKILL thay cho SIGTERM để đảm bảo rằng nó killtree()hoạt động đáng tin cậy không?
davide

3
nếu pskhông hỗ trợ --ppid, người ta có thể sử dụng pgrep -P ${_pid}thay thế
Hubert Kario

16

Lệnh rkill từgói pslist gửi tín hiệu đã cho (hoặcSIGTERMtheo mặc định) đến quy trình được chỉ định và tất cả các hậu duệ của nó:

rkill [-SIG] pid/name...

11

Câu trả lời của brad là những gì tôi cũng khuyên bạn, ngoại trừ việc bạn có thể loại bỏ awkhoàn toàn nếu bạn sử dụng --ppidtùy chọn này ps.

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

Điều này không hiệu quả với tôi trừ khi tôi lấy ra -ax, vì một số lý do (Centos5). Nếu không thì điều này thật tuyệt!
xitrium

9

nếu bạn biết vượt qua được quy trình chính, đây là tập lệnh shell sẽ hoạt động:

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

Một số phiên bản của ps sẽ đưa ra cảnh báo nếu bạn sử dụng "-ax" thay vì "ax". Do đó: đối với trẻ em bằng $ (ps -o pid, ppid ax | \ awk "{if (\ $ 2 == $ pid) {print \ $ 1}}")
Cory R. King

9

Tôi sử dụng một phiên bản sửa đổi một chút của một phương pháp được mô tả ở đây: https://stackoverflow.com/a/5311362/563175

Vì vậy, nó trông như thế:

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

Trong đó, 24901 là PID của cha mẹ.

Nó trông khá xấu xí nhưng nó hoạt động hoàn hảo.


1
Đơn giản hóa với grep, thay vì sed ...pstree -p 24901 | grep -oP '(?<=\()[0-9]+(?=\))'
anishsane

2
bạn nên thêm -lvào pstree, để các dòng dài không bị cắt ngắn; Nó cũng có thể được làm đơn giản hơn để đọc với kill `pstree -l -p 24901 |grep "([[:digit:]]*)" -o |tr -d '()'`(không cần chuyển đổi \nsang không gian vì nó sẽ hoạt động tốt), thx!
Sức mạnh Bảo Bình

9

Phiên bản sửa đổi của câu trả lời của 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

bạn wait $pidchỉ có thể trên các quy trình bạn đã bắt đầu, không phải tất cả các điều kiện tiên quyết, vì vậy đây không phải là một giải pháp chung
Hubert Kario

@Hubert Kario Trong trường hợp đó, chờ đợi sẽ chỉ thoát với trạng thái khác không và tiếp tục thực thi tập lệnh. Tôi có lầm không? Nhưng waitsẽ ngăn chặn Terminatedtin nhắn nếu đó là một đứa trẻ.
x-yuri

9

Tôi không thể nhận xét (không đủ danh tiếng), vì vậy tôi buộc phải thêm một câu trả lời mới , mặc dù đây không thực sự là một câu trả lời.

Có một vấn đề nhỏ với câu trả lời rất hay và kỹ lưỡng khác được đưa ra bởi @olibre vào ngày 28 tháng 2. Đầu ra của ps opgid= $PIDsẽ chứa các khoảng trắng hàng đầu cho một PID ngắn hơn năm chữ số vì psđiều chỉnh cột (sắp xếp các số). Trong toàn bộ dòng lệnh, điều này dẫn đến một dấu âm, theo sau là khoảng trắng, theo sau là nhóm PID. Giải pháp đơn giản là ống psđể trđến chỗ loại bỏ:

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

Cảm ơn bạn tanager. Tôi sẽ sửa câu trả lời của mình;) Chúc mừng
olibre

8

Để thêm vào câu trả lời của Norman Ramsey, có thể đáng để xem tại setsid nếu bạn muốn tạo một nhóm quy trình.
http://pub.opengroup.org/onlinepub/009695399/fifts/setsid.html

Hàm setsid () sẽ tạo một phiên mới, nếu quy trình gọi không phải là người lãnh đạo nhóm quy trình. Khi trả về, quy trình gọi sẽ là người lãnh đạo phiên của phiên mới này, sẽ là người lãnh đạo nhóm quy trình của nhóm quy trình mới và sẽ không có thiết bị đầu cuối kiểm soát. ID nhóm quy trình của quy trình gọi sẽ được đặt bằng ID quy trình của quy trình gọi. Quá trình gọi sẽ là quy trình duy nhất trong nhóm quy trình mới và là quy trình duy nhất trong phiên mới.

Điều tôi muốn nói là bạn có thể tạo một nhóm từ quá trình bắt đầu. Tôi đã sử dụng điều này trong php để có thể giết toàn bộ cây quy trình sau khi khởi động nó.

Đây có thể là một ý tưởng tồi. Tôi sẽ quan tâm đến ý kiến.


Trên thực tế đây là một ý tưởng tuyệt vời và hoạt động rất tốt. Tôi đang sử dụng nó trong trường hợp tôi có thể đặt các quy trình trong cùng một nhóm quy trình (hoặc chúng đã sẵn sàng trong cùng một nhóm).
ốm vào

7

Lấy cảm hứng từ bình luận của ysth

kill -- -PGID

thay vì cho nó một số tiến trình, hãy cho nó phủ định số nhóm. Như thường lệ với hầu hết mọi lệnh, nếu bạn muốn một đối số bình thường bắt đầu bằng một -không được hiểu là một chuyển đổi, hãy đặt trước nó bằng--


Rất tiếc, tôi vừa nhận ra mình đã đưa ra câu trả lời giống như bạn => +1. Nhưng hơn nữa tôi giải thích làm thế nào để đơn giản nhận được PGIDtừ PID. Bạn nghĩ sao? Chúc mừng
olibre

5

Hàm shell sau đây tương tự như nhiều câu trả lời khác, nhưng nó hoạt động cả trên Linux và BSD (OS X, v.v.) mà không phụ thuộc bên ngoài như 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
}

Tôi nghĩ bạn có thêm từ "con" ở cuối dòng thứ 2.
mato

@mato - Đó không phải là thêm. Nó giới hạn phạm vi của $childhàm đó để không làm phiền các biến khác (không cục bộ) có cùng tên và để đảm bảo giá trị của biến cục bộ được dọn sạch sau khi hàm kết thúc.
Adam Katz

Ồ, tôi thấy bây giờ, tôi nghĩ đó là một phần của nhiệm vụ. Tôi đoán tôi nên đọc lại (chậm hơn) trước khi viết bình luận. :) Cảm ơn.
mato

Btw, sẽ không tốt hơn nếu tạo một danh sách trẻ em và sau đó bắt đầu giết từ đầu (cha mẹ)? .. Vì vậy, chúng ta có thể tránh được tình huống khi cha mẹ tái tạo một đứa trẻ hoặc tiếp tục chạy mã tiếp theo có khả năng thay đổi hành vi dự định.
mato

5

Thật dễ dàng để làm điều này với python bằng psutil . Chỉ cần cài đặt psutil với pip và sau đó bạn có một bộ công cụ thao tác quy trình đầy đủ:

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

5

Dựa trên câu trả lời của zhigang, điều này tránh tự sát:

init_killtree() {
    local pid=$1 child

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

4

Nếu bạn muốn giết một quá trình theo tên:

killall -9 -g someprocessname

hoặc là

pgrep someprocessname | xargs pkill -9 -g

3

Đây là phiên bản của tôi để giết tất cả các tiến trình con bằng cách sử dụng tập lệnh bash. Nó không sử dụng đệ quy và phụ thuộc vào lệnh pgrep.

Sử dụng

killtree.sh PID SIGNAL

Nội dung của 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
Điều này sẽ thất bại, nếu các quy trình mới được tạo sau khi danh sách con đã được xây dựng.
ceving

3

Đây là một biến thể của câu trả lời của @ zhigang mà không có AWK, chỉ dựa vào khả năng phân tích cú pháp riêng của 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"
}

Nó dường như hoạt động tốt trên cả máy Mac và Linux. Trong các tình huống mà bạn không thể dựa vào việc có thể quản lý các nhóm quy trình - như khi viết tập lệnh để kiểm tra một phần mềm phải được xây dựng trong nhiều môi trường - kỹ thuật đi trên cây này chắc chắn rất hữu ích.


1

Cảm ơn sự thông thái của bạn, folks. Kịch bản của tôi đã để lại một số tiến trình con khi thoát và mẹo phủ định giúp mọi việc dễ dàng hơn. Tôi đã viết hàm này để được sử dụng trong các tập lệnh khác nếu cần:

# 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
}

Chúc mừng.


1

nếu bạn có pstree và perl trên hệ thống của mình, bạn có thể thử điều này:

perl -e 'kill 9, (`pstree -p PID` =~ m/\((\d+)\)/sg)'

1

Có lẽ tốt hơn là giết cha mẹ trước khi con cái; nếu không, cha mẹ có thể sinh ra những đứa trẻ mới một lần nữa trước khi anh ta tự sát. Những thứ này sẽ sống sót sau khi giết chóc.

Phiên bản ps của tôi khác với phiên bản trên; có lẽ đã quá cũ, do đó, sự kỳ lạ ...

Để sử dụng tập lệnh shell thay vì hàm shell có nhiều lợi thế ...

Tuy nhiên, về cơ bản, đó là ý tưởng của 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

lưu ý rằng tập lệnh @zhighang SIGSTOP xử lý quy trình cha mẹ và truyền tín hiệu cho quá trình dừng, vì vậy điều này không nên (AFAIK) gây ra tình trạng chạy đua giữa quá trình tạo trẻ em và truyền tín hiệu. Phiên bản của bạn mặc dù có cuộc đua giữa việc nhận danh sách trẻ em và gửi tín hiệu cho phụ huynh.
Hubert Kario

1

Những điều sau đây đã được thử nghiệm trên FreeBSD, Linux và MacOS X và chỉ phụ thuộc vào pgrep và kill (các phiên bản ps -o không hoạt động theo BSD). Đối số đầu tiên là pid cha mẹ mà con cái phải được chấm dứt. đối số thứ hai là một boolean để xác định xem pid cha cũng phải được kết thúc hay không.

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

Điều này sẽ gửi SIGTERM cho bất kỳ quá trình con / cháu nào trong tập lệnh shell và nếu SIGTERM không thành công, nó sẽ đợi 10 giây và sau đó gửi kill.


Câu trả lời trước đó:

Cách sau cũng hoạt động nhưng sẽ tự hủy shell trên 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

Tôi phát triển giải pháp của zhigang, xyuri và solidsneck hơn nữa:

 #!/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

Phiên bản này sẽ tránh việc giết chết tổ tiên của nó - điều gây ra một loạt các quá trình con trong các giải pháp trước đó.

Các quy trình được dừng đúng cách trước khi danh sách con được xác định, do đó không có con mới nào được tạo hoặc biến mất.

Sau khi bị giết, các công việc bị dừng phải tiếp tục biến mất khỏi hệ thống.


1

Câu hỏi cũ, tôi biết, nhưng tất cả các câu trả lời dường như tiếp tục gọi ps, điều mà tôi không thích.

Giải pháp dựa trên awk này không yêu cầu đệ quy và chỉ gọi ps một lần.

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

Hoặc trên một dòng đơn:

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

Về cơ bản, ý tưởng là chúng ta xây dựng một mảng (a) cha mẹ: các mục con, sau đó lặp xung quanh mảng tìm con cho cha mẹ phù hợp của chúng ta, thêm chúng vào danh sách cha mẹ (p) khi chúng ta đi.

Nếu bạn không muốn giết quá trình cấp cao nhất, thì hãy làm

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

ngay trước dòng system () sẽ xóa nó khỏi tập kill.

Hãy nhớ rằng có một điều kiện chủng tộc ở đây, nhưng đó là sự thật (theo như tôi có thể thấy) của tất cả các giải pháp. Nó làm những gì tôi cần bởi vì kịch bản tôi cần nó không tạo ra nhiều trẻ em sống ngắn.

Một bài tập cho người đọc sẽ là biến nó thành một vòng lặp 2 lượt: sau lần đầu tiên, gửi SIGSTOP đến tất cả các quy trình trong danh sách p, sau đó lặp lại để chạy lại ps và sau lần chuyển thứ hai gửi SIGTERM, sau đó là SIGCONT. Nếu bạn không quan tâm đến những kết thúc tốt đẹp thì thứ hai có thể chỉ là SIGKILL, tôi cho rằng.


0

Nếu bạn biết mấu chốt của thứ bạn muốn giết, bạn thường có thể đi từ id phiên và mọi thứ trong cùng một phiên. Tôi sẽ kiểm tra lại, nhưng tôi đã sử dụng điều này cho các tập lệnh bắt đầu rsyncs trong các vòng lặp mà tôi muốn chết, và không bắt đầu một đoạn khác (vì vòng lặp) như thể tôi sẽ giết rsync.

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

Nếu bạn không biết pid bạn vẫn có thể làm tổ

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


0

Trong sh lệnh jobs sẽ liệt kê các tiến trình nền. Trong một số trường hợp, tốt hơn hết là giết quy trình mới nhất trước tiên, ví dụ như quy trình cũ hơn đã tạo ra một ổ cắm chung. Trong những trường hợp đó, sắp xếp các PID theo thứ tự ngược lại. Đôi khi bạn muốn chờ giây lát để các công việc viết một cái gì đó lên đĩa hoặc những thứ tương tự trước khi chúng dừng lại.

Và đừng giết nếu bạn không phải làm thế!

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

0

Giết chết tiến trình con trong shell script:

Nhiều lúc chúng ta cần phải giết quá trình con bị treo hoặc chặn vì một số lý do. ví dụ. Sự cố kết nối FTP.

Có hai cách tiếp cận,

1) Để tạo cha mẹ mới riêng biệt cho mỗi đứa trẻ sẽ theo dõi và tiêu diệt quá trình con khi hết thời gian chờ.

Tạo test.sh như sau,

#!/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;

và xem các quá trình có tên là 'thử nghiệm' trong thiết bị đầu cuối khác bằng cách sử dụng lệnh sau.

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

Kịch bản trên sẽ tạo ra 4 tiến trình con mới và cha mẹ của chúng. Mỗi tiến trình con sẽ chạy trong 10 giây. Nhưng một khi hết thời gian tiếp cận 5sec, các quy trình cha mẹ tương ứng sẽ giết chết những đứa trẻ đó. Vì vậy, trẻ sẽ không thể hoàn thành việc thực hiện (10 giây). Chơi xung quanh các thời gian đó (chuyển 10 và 5) để xem hành vi khác. Trong trường hợp đó, đứa trẻ sẽ hoàn thành việc thực hiện trong 5 giây trước khi nó hết thời gian 10 giây.

2) Hãy để phụ huynh hiện tại giám sát và tiêu diệt tiến trình con khi hết thời gian chờ. Điều này sẽ không tạo ra cha mẹ riêng để theo dõi từng đứa trẻ. Ngoài ra, bạn có thể quản lý tất cả các quy trình con đúng trong cùng một cha mẹ.

Tạo test.sh như sau,

#!/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;

và xem các quá trình có tên là 'thử nghiệm' trong thiết bị đầu cuối khác bằng cách sử dụng lệnh sau.

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

Kịch bản trên sẽ tạo ra 4 tiến trình con mới. Chúng tôi đang lưu trữ các pids của tất cả các tiến trình con và lặp qua chúng để kiểm tra xem chúng đã hoàn thành việc thực thi hay vẫn đang chạy. Quá trình con sẽ thực thi cho đến thời gian CMD_TIME. Nhưng nếu đạt đến thời gian chờ của CNT_TIME_OUT, tất cả trẻ em sẽ bị giết bởi quy trình của cha mẹ. Bạn có thể chuyển thời gian và chơi xung quanh với tập lệnh để xem hành vi. Một nhược điểm của phương pháp này là, nó đang sử dụng id nhóm để giết tất cả cây con. Nhưng quá trình cha mẹ tự nó thuộc về cùng một nhóm nên nó cũng sẽ bị giết.

Bạn có thể cần gán id nhóm khác cho quy trình cha nếu bạn không muốn cha mẹ bị giết.

Nhiều thông tin thêm có thế được tìm thấy ở đây,

Giết chết tiến trình con trong shell script


0

Kịch bản này cũng hoạt động:

#/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

Tôi biết đó là cũ, nhưng đó là giải pháp tốt hơn mà tôi tìm thấy:

killtree() { 
    for p in $(pstree -p $1 | grep -o "([[:digit:]]*)" |grep -o "[[:digit:]]*" | tac);do
        echo Terminating: $p 
        kill $p
    done
}
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.