ฉันจะฆ่ากระบวนการ / งานเบื้องหลังได้อย่างไรเมื่อเชลล์สคริปต์ออก


194

ฉันกำลังมองหาวิธีที่จะทำความสะอาดความยุ่งเหยิงเมื่อสคริปต์ระดับสูงของฉันออก

โดยเฉพาะอย่างยิ่งถ้าฉันต้องการใช้set -eฉันหวังว่ากระบวนการพื้นหลังจะตายเมื่อสคริปต์ออก

คำตอบ:


186

เพื่อล้างความยุ่งเหยิงบางอย่างtrapสามารถใช้ มันสามารถให้รายชื่อของสิ่งที่ดำเนินการเมื่อสัญญาณที่เฉพาะเจาะจงมาถึง:

trap "echo hello" SIGINT

แต่ยังสามารถใช้เพื่อดำเนินการบางอย่างหากเชลล์ออก:

trap "killall background" EXIT

มันเป็น builtin ดังนั้นhelp trapจะให้ข้อมูลกับคุณ (ใช้ได้กับการทุบตี) หากคุณต้องการฆ่างานเบื้องหลังเท่านั้นคุณสามารถทำได้

trap 'kill $(jobs -p)' EXIT

ระวังที่จะใช้เดี่ยว'เพื่อป้องกันไม่ให้เปลือกทดแทน$()ทันที


ถ้าอย่างนั้นคุณจะฆ่าเด็กได้อย่างไร? (หรือฉันขาดอะไรบางอย่างที่ชัดเจน)
elmarco

18
killall ฆ่าลูก ๆ ของคุณ แต่ไม่ใช่คุณ
orip

4
kill $(jobs -p)ไม่ทำงานในเส้นประเพราะมันประมวลผลการทดแทนคำสั่งใน subshell (ดูที่การทดแทนคำสั่งใน man dash)
user1431317

8
เป็นที่killall backgroundควรจะเป็นตัวยึดหรือไม่? backgroundไม่ได้อยู่ในหน้าคน ...
Evan Benn

170

มันใช้งานได้สำหรับฉัน (ปรับปรุงให้ดีขึ้นจากผู้แสดงความเห็น):

trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
  • kill -- -$$ส่งSIGTERMไปยังกลุ่มกระบวนการทั้งหมดดังนั้นการฆ่าก็เป็นผู้สืบทอด

  • การระบุสัญญาณEXITมีประโยชน์เมื่อใช้set -e(รายละเอียดเพิ่มเติมที่นี่ )


1
ควรทำงานได้ดีในภาพรวม แต่กระบวนการลูกอาจเปลี่ยนกลุ่มกระบวนการ ในทางตรงกันข้ามมันไม่จำเป็นต้องมีการควบคุมงานและอาจได้รับกระบวนการหลาน ๆ ที่ไม่ได้รับจากการแก้ปัญหาอื่น ๆ
michaeljt

5
หมายเหตุ "kill 0" จะฆ่าสคริปต์ bash หลักด้วย คุณอาจต้องการใช้ "kill - - $ BASHPID" เพื่อฆ่าเฉพาะส่วนย่อยของสคริปต์ปัจจุบัน หากคุณไม่มี $ BASHPID ในเวอร์ชันทุบตีของคุณคุณสามารถส่งออก BASHPID = $ (sh -c 'echo $ PPID')
ACyclic

2
ขอบคุณสำหรับทางออกที่ดีและชัดเจน! น่าเสียดายที่มัน segfaults Bash 4.3 ซึ่งอนุญาตให้เรียกซ้ำกับดักได้ ฉันวิ่งเข้าไปเกี่ยวกับเรื่องนี้4.3.30(1)-releaseใน OSX และก็ยังได้รับการยืนยันบน Ubuntu มีความเป็นwokaround obvoiusแม้ว่า :)
skozin

4
-$$ฉันไม่เข้าใจ มันประเมิน '- <PID> -1234`เช่น ใน kill manpage // builtin manpage เครื่องหมายขีดนำหน้าระบุสัญญาณที่จะส่ง อย่างไรก็ตาม - อาจบล็อกสิ่งนั้น แต่จากนั้นเส้นประนำจะไม่ถูกบันทึกไว้เป็นอย่างอื่น ความช่วยเหลือใด ๆ
Evan Benn

4
@EvanBenn: ตรวจสอบman 2 killซึ่งอธิบายว่าเมื่อ PID เป็นค่าลบสัญญาณจะถูกส่งไปยังกระบวนการทั้งหมดในกลุ่มกระบวนการด้วย ID ที่ระบุ ( en.wikipedia.org/wiki/Process_group ) มันทำให้เกิดความสับสนว่านี้ไม่ได้กล่าวถึงในman 1 killหรือman bashและอาจจะพิจารณาข้อผิดพลาดในเอกสาร
user001

111

อัปเดต: https://stackoverflow.com/a/53714583/302079ปรับปรุงสิ่งนี้โดยการเพิ่มสถานะทางออกและฟังก์ชั่นการล้างข้อมูล

trap "exit" INT TERM
trap "kill 0" EXIT

ทำไมต้องเปลี่ยนINTและTERMออก เพราะทั้งสองควรเรียกใช้kill 0โดยไม่ต้องเข้าสู่วงไม่สิ้นสุด

ทำไมทริกเกอร์kill 0บนEXIT? เนื่องจากการออกจากสคริปต์ปกติควรกระตุ้นkill 0เช่นกัน

ทำไมkill 0? เพราะ subshells ที่ซ้อนกันจะต้องถูกฆ่าเช่นกัน นี้จะใช้เวลาลงต้นไม้กระบวนการทั้งหมด


3
ทางออกเดียวสำหรับกรณีของฉันใน Debian
MindlessRanger

3
ทั้งคำตอบของโยฮันเนสชอบหรือคำตอบที่ได้รับจาก tokland ที่จัดการเพื่อฆ่ากระบวนการพื้นหลังของสคริปต์เชลล์ของฉันเริ่มต้น (บนเดเบียน) วิธีนี้ใช้ได้ผล ฉันไม่รู้ว่าทำไมคำตอบนี้จึงไม่ได้เพิ่มขึ้นอีก คุณช่วยขยายเพิ่มเติมเกี่ยวกับความkill 0หมาย / ความหมายของอะไรได้บ้าง?
josch

7
นี่มันสุดยอดมาก แต่ก็ฆ่าเปลือกแม่ของฉันได้ด้วย :-(
vidstige

5
การแก้ปัญหานี้เกินความจริงอย่างแท้จริง kill 0 (ภายในสคริปต์ของฉัน) ทำลายเซสชั่น X ทั้งหมดของฉัน! บางทีในบางกรณีการฆ่า 0 อาจมีประโยชน์ แต่นี่ไม่ได้เปลี่ยนความจริงที่ว่ามันไม่ใช่ทางออกทั่วไปและควรหลีกเลี่ยงถ้าเป็นไปได้เว้นแต่จะมีเหตุผลที่ดีที่จะใช้มัน มันจะเป็นการดีถ้าเพิ่มคำเตือนว่ามันอาจจะฆ่า parent shell หรือแม้กระทั่งช่วง X ทั้งหมดไม่ใช่แค่งานแบ็คกราวน์ของสคริปต์!
Lissanro Rayen

3
ขณะนี้อาจเป็นวิธีแก้ปัญหาที่น่าสนใจในบางสถานการณ์ตามที่ระบุโดย @vidstige สิ่งนี้จะฆ่ากลุ่มกระบวนการทั้งหมดซึ่งรวมถึงกระบวนการเรียกใช้ (เช่นเชลล์พาเรนต์ในกรณีส่วนใหญ่) ไม่ใช่สิ่งที่คุณต้องการแน่นอนเมื่อคุณเรียกใช้สคริปต์ผ่าน IDE
matpen

21

กับดัก 'kill $ (jobs -p)' EXIT

ฉันจะทำการเปลี่ยนแปลงเพียงเล็กน้อยในคำตอบของ Johannes และใช้งาน -pr เพื่อ จำกัด kill ให้กับกระบวนการที่กำลังรันอยู่และเพิ่มสัญญาณเพิ่มเติมเล็กน้อยในรายการ:

trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT

ทำไมไม่ฆ่างานที่หยุดเช่นกัน? ในกับดัก Bash EXIT จะทำงานในกรณีของ SIGINT และ SIGTERM เช่นกันดังนั้นกับดักจะถูกเรียกสองครั้งในกรณีที่สัญญาณดังกล่าว
jarno

14

trap 'kill 0' SIGINT SIGTERM EXITวิธีการแก้ปัญหาที่อธิบายไว้ในคำตอบของ @ toklandนั้นดีมาก แต่ Bash ล่าสุดล้มเหลวพร้อมกับข้อผิดพลาดในการแยกย่อยเมื่อใช้งาน นั่นเป็นเพราะ Bash เริ่มต้นจาก v. 4.3 อนุญาตการเรียกซ้ำกับดักซึ่งกลายเป็นอนันต์ในกรณีนี้:

  1. กระบวนการเชลล์ได้รับSIGINTหรือSIGTERMหรือEXIT;
  2. สัญญาณได้รับการดักจับดำเนินการkill 0ซึ่งส่งSIGTERMไปยังกระบวนการทั้งหมดในกลุ่มรวมถึงเปลือกตัวเอง;
  3. ไปที่ 1 :)

สิ่งนี้สามารถแก้ไขได้ด้วยการยกเลิกการลงทะเบียนกับดักด้วยตนเอง:

trap 'trap - SIGTERM && kill 0' SIGINT SIGTERM EXIT

วิธีที่น่าสนใจยิ่งขึ้นที่ช่วยให้สามารถพิมพ์สัญญาณที่ได้รับและหลีกเลี่ยงข้อความ "ยุติ:"

#!/usr/bin/env bash

trap_with_arg() { # from https://stackoverflow.com/a/2183063/804678
  local func="$1"; shift
  for sig in "$@"; do
    trap "$func $sig" "$sig"
  done
}

stop() {
  trap - SIGINT EXIT
  printf '\n%s\n' "recieved $1, killing children"
  kill -s SIGINT 0
}

trap_with_arg 'stop' EXIT SIGINT SIGTERM SIGHUP

{ i=0; while (( ++i )); do sleep 0.5 && echo "a: $i"; done } &
{ i=0; while (( ++i )); do sleep 0.6 && echo "b: $i"; done } &

while true; do read; done

UPD : เพิ่มตัวอย่างน้อยที่สุด; stopฟังก์ชั่นที่ได้รับการปรับปรุงเพื่อกำจัดสัญญาณที่ไม่จำเป็น aviod และการซ่อนข้อความ "Terminated:" จากเอาท์พุท ขอบคุณTrevor Boyd Smithสำหรับคำแนะนำ!


ในstop()คุณให้อาร์กิวเมนต์แรกเป็นหมายเลขสัญญาณ แต่จากนั้นคุณ hardcode สิ่งที่สัญญาณถูกลงทะเบียน แทนที่จะ hardcode สัญญาณที่ถูกลงทะเบียนคุณสามารถใช้อาร์กิวเมนต์แรกเพื่อยกเลิกการลงทะเบียนในstop()ฟังก์ชั่น (การทำเช่นนั้นอาจหยุดสัญญาณ recursive อื่น ๆ (นอกเหนือจาก 3 hardcoded))
Trevor Boyd Smith

@ TrevorBoydSmith นี้จะไม่ทำงานตามที่คาดไว้ฉันเดา ตัวอย่างเช่นเชลล์อาจถูกฆ่าด้วยSIGINTแต่kill 0ส่งSIGTERMซึ่งจะติดกับดักอีกครั้ง สิ่งนี้จะไม่ทำให้เกิดการวนซ้ำแบบไม่มีที่สิ้นสุดเนื่องจากSIGTERMจะไม่ถูกดักระหว่างการstopโทรครั้งที่สอง
skozin

น่าtrap - $1 && kill -s $1 0จะทำงานได้ดีขึ้น ฉันจะทดสอบและอัปเดตคำตอบนี้ ขอบคุณสำหรับความคิดที่ดี! :)
skozin

ไม่trap - $1 && kill -s $1 0ทำงานไม่ได้เหมือนกันเพราะเราไม่สามารถฆ่าEXITได้ แต่มันก็เพียงพอแล้วที่จะทำ de-trap TERMเพราะkillส่งสัญญาณนี้โดยปริยาย
skozin

ผมทดสอบการเรียกซ้ำกับEXITที่trapสัญญาณจัดการอยู่เสมอดำเนินการเพียงครั้งเดียว
Trevor Boyd Smith

9

จะอยู่ในด้านความปลอดภัยฉันคิดว่ามันจะดีกว่าที่จะกำหนดฟังก์ชั่นการทำความสะอาดและเรียกมันจากกับดัก:

cleanup() {
        local pids=$(jobs -pr)
        [ -n "$pids" ] && kill $pids
}
trap "cleanup" INT QUIT TERM EXIT [...]

หรือหลีกเลี่ยงฟังก์ชั่นทั้งหมด:

trap '[ -n "$(jobs -pr)" ] && kill $(jobs -pr)' INT QUIT TERM EXIT [...]

ทำไม? เพราะเพียงแค่ใช้trap 'kill $(jobs -pr)' [...]หนึ่งสมมติว่าจะมีงานพื้นหลังทำงานเมื่อสภาพสัญญาณกับดักสัญญาณ เมื่อไม่มีงานใดจะเห็นข้อความ (หรือคล้ายกัน) ต่อไปนี้:

kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]

เพราะว่างjobs -prเปล่า - ฉันลงเอยด้วย 'กับดัก' (ตั้งใจจะเล่น)


กรณีทดสอบนี้ใช้[ -n "$(jobs -pr)" ]ไม่ได้กับการทุบตีของฉัน ฉันใช้ GNU bash รุ่น 4.2.46 (2) - ปล่อย (x86_64-redhat-linux-gnu) ข้อความ "kill: usage" ยังคงปรากฏขึ้นอีก
Douwe van der Leest

ฉันสงสัยว่ามันเกี่ยวข้องกับความจริงที่jobs -prว่าไม่ได้คืนค่า PID ของลูก ๆ ของกระบวนการพื้นหลัง มันไม่ได้ฉีกทรีกระบวนการทั้งหมดลงเพียงตัดรากออกเท่านั้น
Douwe van der Leest

2

รุ่นที่ดีที่ทำงานภายใต้ Linux, BSD และ MacOS X ขั้นแรกพยายามส่ง SIGTERM และหากไม่สำเร็จให้ดำเนินการตามขั้นตอนนี้ภายใน 10 วินาที

KillJobs() {
    for job in $(jobs -p); do
            kill -s SIGTERM $job > /dev/null 2>&1 || (sleep 10 && kill -9 $job > /dev/null 2>&1 &)

    done
}

TrapQuit() {
    # Whatever you need to clean here
    KillJobs
}

trap TrapQuit EXIT

โปรดทราบว่างานไม่ได้รวมกระบวนการลูกหลาน



1

อีกตัวเลือกหนึ่งคือให้สคริปต์ตั้งค่าตัวเองเป็นหัวหน้ากลุ่มกระบวนการและดักจับ killpg ในกลุ่มกระบวนการของคุณเมื่อออก


คุณตั้งค่ากระบวนการเป็นหัวหน้ากลุ่มกระบวนการได้อย่างไร "killpg" คืออะไร?
jarno

0

ดังนั้นสคริปต์การโหลดสคริปต์ เรียกใช้killallคำสั่ง (หรืออะไรก็ตามที่มีอยู่ในระบบปฏิบัติการของคุณ) ที่ดำเนินการทันทีที่สคริปต์เสร็จสิ้น


0

jobs -p ไม่ทำงานในเชลล์ทั้งหมดหากถูกเรียกใน sub-shell อาจเป็นไปได้ว่าจะไม่เปลี่ยนเส้นทางไปที่ไฟล์ แต่ไม่ใช่ไพพ์ (ฉันคิดว่าเดิมทีมีไว้สำหรับการใช้แบบโต้ตอบเท่านั้น)

เกี่ยวกับสิ่งต่อไปนี้:

trap 'while kill %% 2>/dev/null; do jobs > /dev/null; done' INT TERM EXIT [...]

จำเป็นต้องเรียกใช้ "งาน" ด้วยเปลือกของเดเบียนซึ่งไม่สามารถอัปเดตงานปัจจุบัน ("%%") หากขาดหายไป


อืมวิธีการที่น่าสนใจ แต่มันดูเหมือนจะไม่ทำงาน พิจารณาเรื่องที่สนใจtrap 'echo in trap; set -x; trap - TERM EXIT; while kill %% 2>/dev/null; do jobs > /dev/null; done; set +x' INT TERM EXIT; sleep 100 & while true; do printf .; sleep 1; doneหากคุณเรียกใช้ใน Bash (5.0.3) และพยายามที่จะยุติดูเหมือนว่าจะมีวงไม่สิ้นสุด อย่างไรก็ตามหากคุณยกเลิกอีกครั้งก็ใช้งานได้ แม้ใน Dash (0.5.10.2-6) คุณต้องยุติมันสองครั้ง
jarno

0

ฉันได้ทำการดัดแปลงคำตอบของ @ tokland รวมกับความรู้จากhttp://veithen.github.io/2014/11/16/sigterm-propagation.htmlเมื่อฉันสังเกตเห็นtrapว่าไม่ได้กระตุ้นถ้าฉันใช้กระบวนการพื้นหน้า (ไม่มีพื้นหลัง&):

#!/bin/bash

# killable-shell.sh: Kills itself and all children (the whole process group) when killed.
# Adapted from http://stackoverflow.com/a/2173421 and http://veithen.github.io/2014/11/16/sigterm-propagation.html
# Note: Does not work (and cannot work) when the shell itself is killed with SIGKILL, for then the trap is not triggered.
trap "trap - SIGTERM && echo 'Caught SIGTERM, sending SIGTERM to process group' && kill -- -$$" SIGINT SIGTERM EXIT

echo $@
"$@" &
PID=$!
wait $PID
trap - SIGINT SIGTERM EXIT
wait $PID

ตัวอย่างของการทำงาน:

$ bash killable-shell.sh sleep 100
sleep 100
^Z
[1]  + 31568 suspended  bash killable-shell.sh sleep 100

$ ps aux | grep "sleep"
niklas   31568  0.0  0.0  19640  1440 pts/18   T    01:30   0:00 bash killable-shell.sh sleep 100
niklas   31569  0.0  0.0  14404   616 pts/18   T    01:30   0:00 sleep 100
niklas   31605  0.0  0.0  18956   936 pts/18   S+   01:30   0:00 grep --color=auto sleep

$ bg
[1]  + 31568 continued  bash killable-shell.sh sleep 100

$ kill 31568
Caught SIGTERM, sending SIGTERM to process group
[1]  + 31568 terminated  bash killable-shell.sh sleep 100

$ ps aux | grep "sleep"
niklas   31717  0.0  0.0  18956   936 pts/18   S+   01:31   0:00 grep --color=auto sleep

0

เพื่อความหลากหลายฉันจะโพสต์รูปแบบของhttps://stackoverflow.com/a/2173421/102484เนื่องจากโซลูชันนั้นนำไปสู่ข้อความ "ยุติ" ในสภาพแวดล้อมของฉัน:

trap 'test -z "$intrap" && export intrap=1 && kill -- -$$' SIGINT SIGTERM EXIT
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.