ฉันกำลังมองหาวิธีที่จะทำความสะอาดความยุ่งเหยิงเมื่อสคริปต์ระดับสูงของฉันออก
โดยเฉพาะอย่างยิ่งถ้าฉันต้องการใช้set -e
ฉันหวังว่ากระบวนการพื้นหลังจะตายเมื่อสคริปต์ออก
ฉันกำลังมองหาวิธีที่จะทำความสะอาดความยุ่งเหยิงเมื่อสคริปต์ระดับสูงของฉันออก
โดยเฉพาะอย่างยิ่งถ้าฉันต้องการใช้set -e
ฉันหวังว่ากระบวนการพื้นหลังจะตายเมื่อสคริปต์ออก
คำตอบ:
เพื่อล้างความยุ่งเหยิงบางอย่างtrap
สามารถใช้ มันสามารถให้รายชื่อของสิ่งที่ดำเนินการเมื่อสัญญาณที่เฉพาะเจาะจงมาถึง:
trap "echo hello" SIGINT
แต่ยังสามารถใช้เพื่อดำเนินการบางอย่างหากเชลล์ออก:
trap "killall background" EXIT
มันเป็น builtin ดังนั้นhelp trap
จะให้ข้อมูลกับคุณ (ใช้ได้กับการทุบตี) หากคุณต้องการฆ่างานเบื้องหลังเท่านั้นคุณสามารถทำได้
trap 'kill $(jobs -p)' EXIT
ระวังที่จะใช้เดี่ยว'
เพื่อป้องกันไม่ให้เปลือกทดแทน$()
ทันที
kill $(jobs -p)
ไม่ทำงานในเส้นประเพราะมันประมวลผลการทดแทนคำสั่งใน subshell (ดูที่การทดแทนคำสั่งใน man dash)
killall background
ควรจะเป็นตัวยึดหรือไม่? background
ไม่ได้อยู่ในหน้าคน ...
มันใช้งานได้สำหรับฉัน (ปรับปรุงให้ดีขึ้นจากผู้แสดงความเห็น):
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
4.3.30(1)-release
ใน OSX และก็ยังได้รับการยืนยันบน Ubuntu มีความเป็นwokaround obvoiusแม้ว่า :)
-$$
ฉันไม่เข้าใจ มันประเมิน '- <PID> -1234
`เช่น ใน kill manpage // builtin manpage เครื่องหมายขีดนำหน้าระบุสัญญาณที่จะส่ง อย่างไรก็ตาม - อาจบล็อกสิ่งนั้น แต่จากนั้นเส้นประนำจะไม่ถูกบันทึกไว้เป็นอย่างอื่น ความช่วยเหลือใด ๆ
man 2 kill
ซึ่งอธิบายว่าเมื่อ PID เป็นค่าลบสัญญาณจะถูกส่งไปยังกระบวนการทั้งหมดในกลุ่มกระบวนการด้วย ID ที่ระบุ ( en.wikipedia.org/wiki/Process_group ) มันทำให้เกิดความสับสนว่านี้ไม่ได้กล่าวถึงในman 1 kill
หรือman bash
และอาจจะพิจารณาข้อผิดพลาดในเอกสาร
อัปเดต: 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 ที่ซ้อนกันจะต้องถูกฆ่าเช่นกัน นี้จะใช้เวลาลงต้นไม้กระบวนการทั้งหมด
kill 0
หมาย / ความหมายของอะไรได้บ้าง?
กับดัก 'kill $ (jobs -p)' EXIT
ฉันจะทำการเปลี่ยนแปลงเพียงเล็กน้อยในคำตอบของ Johannes และใช้งาน -pr เพื่อ จำกัด kill ให้กับกระบวนการที่กำลังรันอยู่และเพิ่มสัญญาณเพิ่มเติมเล็กน้อยในรายการ:
trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT
trap 'kill 0' SIGINT SIGTERM EXIT
วิธีการแก้ปัญหาที่อธิบายไว้ในคำตอบของ @ toklandนั้นดีมาก แต่ Bash ล่าสุดล้มเหลวพร้อมกับข้อผิดพลาดในการแยกย่อยเมื่อใช้งาน นั่นเป็นเพราะ Bash เริ่มต้นจาก v. 4.3 อนุญาตการเรียกซ้ำกับดักซึ่งกลายเป็นอนันต์ในกรณีนี้:
SIGINT
หรือSIGTERM
หรือEXIT
;kill 0
ซึ่งส่งSIGTERM
ไปยังกระบวนการทั้งหมดในกลุ่มรวมถึงเปลือกตัวเอง;สิ่งนี้สามารถแก้ไขได้ด้วยการยกเลิกการลงทะเบียนกับดักด้วยตนเอง:
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))
SIGINT
แต่kill 0
ส่งSIGTERM
ซึ่งจะติดกับดักอีกครั้ง สิ่งนี้จะไม่ทำให้เกิดการวนซ้ำแบบไม่มีที่สิ้นสุดเนื่องจากSIGTERM
จะไม่ถูกดักระหว่างการstop
โทรครั้งที่สอง
trap - $1 && kill -s $1 0
จะทำงานได้ดีขึ้น ฉันจะทดสอบและอัปเดตคำตอบนี้ ขอบคุณสำหรับความคิดที่ดี! :)
trap - $1 && kill -s $1 0
ทำงานไม่ได้เหมือนกันเพราะเราไม่สามารถฆ่าEXIT
ได้ แต่มันก็เพียงพอแล้วที่จะทำ de-trap TERM
เพราะkill
ส่งสัญญาณนี้โดยปริยาย
EXIT
ที่trap
สัญญาณจัดการอยู่เสมอดำเนินการเพียงครั้งเดียว
จะอยู่ในด้านความปลอดภัยฉันคิดว่ามันจะดีกว่าที่จะกำหนดฟังก์ชั่นการทำความสะอาดและเรียกมันจากกับดัก:
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" ยังคงปรากฏขึ้นอีก
jobs -pr
ว่าไม่ได้คืนค่า PID ของลูก ๆ ของกระบวนการพื้นหลัง มันไม่ได้ฉีกทรีกระบวนการทั้งหมดลงเพียงตัดรากออกเท่านั้น
รุ่นที่ดีที่ทำงานภายใต้ 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
โปรดทราบว่างานไม่ได้รวมกระบวนการลูกหลาน
function cleanup_func {
sleep 0.5
echo cleanup
}
trap "exit \$exit_code" INT TERM
trap "exit_code=\$?; cleanup_func; kill 0" EXIT
# exit 1
# exit 0
เช่นเดียวกับhttps://stackoverflow.com/a/22644006/10082476แต่มีรหัสทางออกที่เพิ่ม
exit_code
ได้มาจากในINT TERM
กับดัก?
อีกตัวเลือกหนึ่งคือให้สคริปต์ตั้งค่าตัวเองเป็นหัวหน้ากลุ่มกระบวนการและดักจับ killpg ในกลุ่มกระบวนการของคุณเมื่อออก
ดังนั้นสคริปต์การโหลดสคริปต์ เรียกใช้killall
คำสั่ง (หรืออะไรก็ตามที่มีอยู่ในระบบปฏิบัติการของคุณ) ที่ดำเนินการทันทีที่สคริปต์เสร็จสิ้น
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) คุณต้องยุติมันสองครั้ง
ฉันได้ทำการดัดแปลงคำตอบของ @ 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
เพื่อความหลากหลายฉันจะโพสต์รูปแบบของhttps://stackoverflow.com/a/2173421/102484เนื่องจากโซลูชันนั้นนำไปสู่ข้อความ "ยุติ" ในสภาพแวดล้อมของฉัน:
trap 'test -z "$intrap" && export intrap=1 && kill -- -$$' SIGINT SIGTERM EXIT