วิธีใช้สัญญาณลินุกซ์ในสคริปต์ทุบตี
เผยแพร่แล้ว: 2022-08-09
เคอร์เนล Linux ส่งสัญญาณไปยังกระบวนการเกี่ยวกับเหตุการณ์ที่จำเป็นต้องตอบสนอง สคริปต์ที่ประพฤติตัวดีจะจัดการกับสัญญาณได้อย่างสวยงามและแข็งแกร่ง และสามารถล้างออกได้แม้คุณจะกด Ctrl+C นี่คือวิธีการ
สัญญาณและกระบวนการ
สัญญาณเป็นข้อความทางเดียวที่สั้น รวดเร็ว ที่ส่งไปยังกระบวนการต่างๆ เช่น สคริปต์ โปรแกรม และภูต พวกเขาแจ้งให้กระบวนการทราบเกี่ยวกับสิ่งที่เกิดขึ้น ผู้ใช้อาจกด Ctrl+C หรือแอปพลิเคชันอาจพยายามเขียนไปยังหน่วยความจำที่ไม่สามารถเข้าถึงได้
หากผู้เขียนกระบวนการคาดการณ์ว่าอาจมีการส่งสัญญาณบางอย่างไป พวกเขาสามารถเขียนรูทีนลงในโปรแกรมหรือสคริปต์เพื่อจัดการกับสัญญาณนั้นได้ กิจวัตรดังกล่าวเรียกว่า ตัวจัดการสัญญาณ มันจับหรือดักสัญญาณ และดำเนินการบางอย่างเพื่อตอบสนองต่อสัญญาณนั้น
ลินุกซ์ใช้สัญญาณจำนวนมากอย่างที่เราเห็น แต่จากมุมมองของการเขียนสคริปต์ มีเพียงกลุ่มย่อยของสัญญาณที่คุณน่าจะสนใจ โดยเฉพาะอย่างยิ่ง ในสคริปต์ที่ไม่สำคัญ สัญญาณที่บอก สคริปต์ที่จะปิดควรติดอยู่ (ถ้าเป็นไปได้) และดำเนินการปิดอย่างสง่างาม
ตัวอย่างเช่น สคริปต์ที่สร้างไฟล์ชั่วคราวหรือพอร์ตไฟร์วอลล์ที่เปิดอยู่สามารถให้โอกาสในการลบไฟล์ชั่วคราวหรือปิดพอร์ตก่อนที่จะปิดตัวลง หากสคริปต์ตายทันทีที่รับสัญญาณ คอมพิวเตอร์ของคุณอาจถูกปล่อยให้อยู่ในสถานะที่คาดเดาไม่ได้
นี่คือวิธีที่คุณสามารถจัดการกับสัญญาณในสคริปต์ของคุณเองได้
พบกับสัญญาณ
คำสั่ง Linux บางคำสั่งมีชื่อที่คลุมเครือ ไม่ใช่คำสั่งดักจับสัญญาณ เรียกว่า trap
เรายังสามารถใช้ trap
ด้วยตัวเลือก -l
(รายการ) เพื่อแสดงรายการสัญญาณทั้งหมดที่ลีนุกซ์ใช้
กับดัก -l
แม้ว่ารายการลำดับเลขของเราจะสิ้นสุดที่ 64 แต่จริงๆ แล้วมี 62 สัญญาณ สัญญาณ 32 และ 33 หายไป ไม่ได้ใช้งานใน Linux ฟังก์ชันเหล่านี้ถูกแทนที่ด้วยฟังก์ชันในคอมไพเลอร์ gcc
สำหรับจัดการเธรดแบบเรียลไทม์ ทุกอย่างตั้งแต่สัญญาณ 34, SIGRTMIN
ไปจนถึงสัญญาณ 64, SIGRTMAX
เป็นสัญญาณแบบเรียลไทม์
คุณจะเห็นรายการต่างๆ ในระบบปฏิบัติการที่คล้าย Unix ที่แตกต่างกัน ตัวอย่างเช่นใน OpenIndiana มีสัญญาณ 32 และ 33 พร้อมกับสัญญาณพิเศษจำนวนมากที่นับรวมเป็น 73
สามารถอ้างอิงสัญญาณตามชื่อ หมายเลข หรือชื่อย่อได้ ชื่อย่อของพวกเขาเป็นเพียงชื่อที่มีการนำ “SIG” ออก
สัญญาณขึ้นด้วยเหตุผลหลายประการ หากคุณสามารถถอดรหัสได้ จุดประสงค์ของพวกเขาจะอยู่ในชื่อของพวกเขา ผลกระทบของสัญญาณตกอยู่ในประเภทใดประเภทหนึ่ง:
- ยุติ: กระบวนการสิ้นสุดลง
- ละเว้น: สัญญาณไม่ส่งผลต่อกระบวนการ นี่เป็นสัญญาณข้อมูลเท่านั้น
- แกนหลัก: ไฟล์ดัมพ์คอร์ถูกสร้างขึ้น ซึ่งมักจะเกิดขึ้นเนื่องจากกระบวนการละเมิดในทางใดทางหนึ่ง เช่น การละเมิดหน่วยความจำ
- หยุด: กระบวนการหยุดทำงาน นั่นคือ หยุดชั่วคราว ไม่สิ้นสุด
- ดำเนินการ ต่อ: บอกกระบวนการที่หยุดทำงานเพื่อดำเนินการต่อไป
นี่คือสัญญาณที่คุณจะเจอบ่อยที่สุด
- SIGHUP : สัญญาณ 1. การเชื่อมต่อกับโฮสต์ระยะไกล เช่น เซิร์ฟเวอร์ SSH หลุดโดยไม่คาดคิดหรือผู้ใช้ออกจากระบบ สคริปต์ที่รับสัญญาณนี้อาจยุติลงอย่างสวยงาม หรืออาจเลือกที่จะพยายามเชื่อมต่อกับโฮสต์ระยะไกลอีกครั้ง
- SIGINT : สัญญาณ 2 ผู้ใช้กด Ctrl+C เพื่อบังคับให้กระบวนการปิด หรือใช้คำสั่ง
kill
กับสัญญาณ 2 ในทางเทคนิค นี่คือสัญญาณขัดจังหวะ ไม่ใช่สัญญาณสิ้นสุด แต่เป็นสคริปต์ที่ถูกขัดจังหวะโดยไม่มี ตัวจัดการสัญญาณมักจะยุติ - SIGQUIT : สัญญาณ 3 ผู้ใช้กดชุดค่าผสม Ctrl+D เพื่อบังคับให้ออกจากกระบวนการ หรือใช้คำสั่ง
kill
กับสัญญาณ 3 - SIGFPE : สัญญาณ 8 กระบวนการพยายามดำเนินการทางคณิตศาสตร์ที่ผิดกฎหมาย (เป็นไปไม่ได้) เช่น การหารด้วยศูนย์
- SIGKILL : สัญญาณ 9. นี่คือสัญญาณที่เทียบเท่ากับกิโยติน คุณไม่สามารถจับหรือเพิกเฉยได้ และมันก็เกิดขึ้นทันที กระบวนการนี้จะสิ้นสุดลงทันที
- SIGTERM : สัญญาณ 15. นี่คือ
SIGKILL
เวอร์ชันที่มีน้ำใจมากกว่าSIGTERM
ยังบอกให้กระบวนการยุติการทำงาน แต่สามารถดักจับและกระบวนการสามารถเรียกใช้กระบวนการล้างข้อมูลก่อนที่จะปิดตัวลง ซึ่งจะทำให้การปิดระบบเป็นไปอย่างราบรื่น นี่คือสัญญาณดีฟอลต์ที่เกิดขึ้นจากคำสั่งkill
สัญญาณบน Command Line
วิธีหนึ่งในการดักจับสัญญาณคือการใช้ trap
ที่มีหมายเลขหรือชื่อของสัญญาณ และการตอบสนองที่คุณต้องการให้เกิดขึ้นหากได้รับสัญญาณ เราสามารถสาธิตสิ่งนี้ได้ในหน้าต่างเทอร์มินัล
คำสั่งนี้ดักจับสัญญาณ SIGINT
การตอบสนองคือการพิมพ์บรรทัดข้อความไปที่หน้าต่างเทอร์มินัล เรากำลังใช้ตัวเลือก -e
(เปิดใช้งาน Escapes) กับ echo
เพื่อให้เราสามารถใช้ตัวระบุรูปแบบ “ \n
”
กับดัก 'echo -e "\nCtrl+c ตรวจพบแล้ว"' SIGINT
บรรทัดข้อความของเราถูกพิมพ์ทุกครั้งที่เรากด Ctrl+C รวมกัน
หากต้องการดูว่ามีการตั้งค่ากับดักบนสัญญาณหรือไม่ ให้ใช้ตัวเลือก -p
(การพิมพ์กับดัก)
กับดัก -p SIGINT
การใช้ trap
ที่ไม่มีตัวเลือกจะทำสิ่งเดียวกัน
ในการรีเซ็ตสัญญาณเป็นสถานะปกติที่ไม่มีการดักจับ ให้ใช้เครื่องหมายยัติภังค์ “ -
” และชื่อของสัญญาณที่ติดอยู่
กับดัก - SIGINT
กับดัก -p SIGINT
ไม่มีเอาต์พุตจากคำสั่ง trap -p
บ่งชี้ว่าไม่มีการตั้งค่ากับดักบนสัญญาณนั้น
ดักจับสัญญาณในสคริปต์
เราสามารถใช้คำสั่ง trap
รูปแบบทั่วไปเดียวกันภายในสคริปต์ได้ สคริปต์นี้ดักจับสัญญาณที่แตกต่างกันสามแบบ SIGINT
, SIGQUIT
และ SIGTERM
#!/bin/bash กับดัก "echo ฉันถูก SIGINT ถูกยกเลิก ออก" SIGINT กับดัก "echo ฉันถูก SIGQUIT ถูกยกเลิก ออก" SIGQUIT กับดัก "echo ฉันถูก SIGTERM ถูกยกเลิก ออก" SIGTERM เสียงสะท้อน $$ เคาน์เตอร์=0 ในขณะที่จริง ทำ echo "หมายเลขลูป:" $((++ตัวนับ)) นอน 1 เสร็จแล้ว
คำสั่ง trap
สามประโยคอยู่ที่ด้านบนสุดของสคริปต์ โปรดทราบว่าเราได้รวมคำสั่ง exit
ไว้ในการตอบสนองต่อแต่ละสัญญาณ ซึ่งหมายความว่าสคริปต์ตอบสนองต่อสัญญาณแล้วออก
คัดลอกข้อความลงในโปรแกรมแก้ไขและบันทึกลงในไฟล์ชื่อ "simple-loop.sh" และทำให้สามารถเรียกใช้งานได้โดยใช้คำสั่ง chmod
คุณจะต้องทำอย่างนั้นกับสคริปต์ทั้งหมดในบทความนี้หากต้องการติดตามบนคอมพิวเตอร์ของคุณเอง เพียงใช้ชื่อสคริปต์ที่เหมาะสมในแต่ละกรณี

chmod +x simple-loop.sh
ส่วนที่เหลือของสคริปต์นั้นง่ายมาก เราจำเป็นต้องทราบ ID กระบวนการของสคริปต์ ดังนั้นเราจึงมีสคริปต์ที่สะท้อนถึงเรา ตัวแปร $$
เก็บ ID กระบวนการของสคริปต์
เราสร้างตัวแปรที่เรียกว่าตัว counter
และตั้งค่าให้เป็นศูนย์
วง while
จะทำงานตลอดไปเว้นแต่จะถูกบังคับให้หยุด มันเพิ่มตัวแปรตัว counter
สะท้อนไปที่หน้าจอ และพักสักครู่
มาเรียกใช้สคริปต์และส่งสัญญาณที่แตกต่างกันไป
./simple-loop.sh
เมื่อเรากด "Ctrl+C" ข้อความของเราจะถูกพิมพ์ไปที่หน้าต่างเทอร์มินัลและสคริปต์จะสิ้นสุดลง
เรียกใช้อีกครั้งและส่งสัญญาณ SIGQUIT
โดยใช้คำสั่ง kill
เราจะต้องทำสิ่งนั้นจากหน้าต่างเทอร์มินัลอื่น คุณจะต้องใช้รหัสกระบวนการที่รายงานโดยสคริปต์ของคุณเอง
./simple-loop.sh
ฆ่า -SIGQUIT 4575
ตามที่คาดไว้สคริปต์รายงานว่าสัญญาณมาถึงแล้วจะสิ้นสุดลง และสุดท้าย เพื่อพิสูจน์ประเด็น เราจะทำอีกครั้งด้วยสัญญาณ SIGTERM
./simple-loop.sh
ฆ่า -SIGTERM 4584
เราได้ตรวจสอบแล้วว่าสามารถดักจับสัญญาณหลายตัวในสคริปต์ และตอบสนองต่อสัญญาณแต่ละตัวแยกจากกัน ขั้นตอนที่ส่งเสริมสิ่งเหล่านี้ตั้งแต่น่าสนใจไปจนถึงมีประโยชน์คือการเพิ่มตัวจัดการสัญญาณ
การจัดการสัญญาณในสคริปต์
เราสามารถแทนที่สตริงการตอบกลับด้วยชื่อของฟังก์ชันในสคริปต์ของคุณได้ คำสั่ง trap
จะเรียกใช้ฟังก์ชันนั้นเมื่อตรวจพบสัญญาณ
คัดลอกข้อความนี้ไปยังโปรแกรมแก้ไขและบันทึกเป็นไฟล์ชื่อ "grace.sh" และทำให้สามารถเรียกใช้งานได้ด้วย chmod
#!/bin/bash กับดัก graceful_shutdown SIGINT SIGQUIT SIGTERM สง่างาม_shutdown() { echo -e "\nการลบไฟล์ชั่วคราว:" $temp_file rm -rf "$temp_file" ทางออก } temp_file=$(mktemp -p /tmp tmp.XXXXXXXXXX) echo "ไฟล์ชั่วคราวที่สร้าง:" $temp_file เคาน์เตอร์=0 ในขณะที่จริง ทำ echo "หมายเลขลูป:" $((++ตัวนับ)) นอน 1 เสร็จแล้ว
สคริปต์ตั้งค่ากับดักสำหรับสัญญาณที่แตกต่างกันสามแบบ— SIGHUP
, SIGINT
และ SIGTERM
— โดยใช้คำสั่ง trap
เดียว คำตอบคือชื่อของฟังก์ชัน graceful_shutdown()
ฟังก์ชันนี้จะถูกเรียกเมื่อใดก็ตามที่ได้รับสัญญาณที่ติดอยู่หนึ่งในสาม
สคริปต์สร้างไฟล์ชั่วคราวในไดเร็กทอรี “/tmp” โดยใช้ mktemp
เทมเพลตชื่อไฟล์คือ “tmp.XXXXXXXXXX” ดังนั้นชื่อไฟล์จะเป็น “tmp” ตามด้วยตัวอักษรและตัวเลขสุ่มสิบตัว ชื่อของไฟล์สะท้อนบนหน้าจอ
สคริปต์ที่เหลือจะเหมือนกับสคริปต์ก่อนหน้า โดยมีตัวแปรตัว counter
while
ลูปแบบอนันต์
./grace.sh
เมื่อไฟล์ถูกส่งสัญญาณที่ทำให้ปิด ฟังก์ชัน graceful_shutdown()
จะถูกเรียก การดำเนินการนี้จะลบไฟล์ชั่วคราวไฟล์เดียวของเรา ในสถานการณ์จริง มันสามารถดำเนินการล้างสคริปต์ที่คุณต้องการได้
นอกจากนี้เรายังรวมสัญญาณที่ติดอยู่ทั้งหมดเข้าด้วยกันและจัดการด้วยฟังก์ชันเดียว คุณสามารถดักจับสัญญาณทีละตัวและส่งไปยังฟังก์ชันตัวจัดการเฉพาะของตนเองได้
คัดลอกข้อความนี้และบันทึกในไฟล์ชื่อ “triple.sh” และทำให้สามารถเรียกใช้งานได้โดยใช้คำสั่ง chmod
#!/bin/bash กับดัก sigint_handler SIGINT กับดัก sigusr1_handler SIGUSR1 กับดัก exit_handler EXIT ฟังก์ชัน sigint_handler() { ((++signt_count)) echo -e "\nSIGINT ได้รับ $sigint_count ครั้ง" ถ้า [[ "$sigint_count" -eq 3 ]]; แล้ว echo "กำลังเริ่มปิดตัวลง" loop_flag=1 fi } ฟังก์ชัน sigusr1_handler () { echo "SIGUSR1 ส่งและรับ $((++sigusr1_count)) ครั้ง" } ฟังก์ชัน exit_handler() { echo "ตัวจัดการการออก: สคริปต์กำลังปิดตัวลง ... " } เสียงสะท้อน $$ sigusr1_count=0 signt_count=0 loop_flag=0 ในขณะที่ [[ $loop_flag -eq 0 ]]; ทำ ฆ่า -SIGUSR1 $$ นอน 1 เสร็จแล้ว
เรากำหนดกับดักสามอันที่ด้านบนของสคริปต์
- หนึ่งกับดัก
SIGINT
และมีตัวจัดการชื่อsigint_handler()
- ตัวที่สองดักจับสัญญาณที่เรียกว่า
SIGUSR1
และใช้ตัวจัดการที่เรียกว่าsigusr1_handler()
- กับดักหมายเลขสามดักสัญญาณ
EXIT
สัญญาณนี้ถูกยกขึ้นโดยสคริปต์เองเมื่อปิด การตั้งค่าตัวจัดการสัญญาณสำหรับEXIT
หมายความว่าคุณสามารถตั้งค่าฟังก์ชันที่จะเรียกใช้เสมอเมื่อสคริปต์หยุดทำงาน (เว้นแต่จะถูกฆ่าด้วยสัญญาณSIGKILL
) ตัวจัดการของเราเรียกว่าexit_handler()
SIGUSR1
และ SIGUSR2
เป็นสัญญาณที่ให้ไว้เพื่อให้คุณสามารถส่งสัญญาณที่กำหนดเองไปยังสคริปต์ของคุณได้ วิธีที่คุณตีความและโต้ตอบกับพวกเขานั้นขึ้นอยู่กับคุณทั้งหมด
ปล่อยให้ตัวจัดการสัญญาณกันไว้ก่อน เนื้อหาของสคริปต์น่าจะคุ้นเคยสำหรับคุณ มันสะท้อนรหัสกระบวนการไปที่หน้าต่างเทอร์มินัลและสร้างตัวแปรบางตัว ตัวแปร sigusr1_count
บันทึกจำนวนครั้งที่ SIGUSR1
ถูกจัดการ และ sigint_count
บันทึกจำนวนครั้งที่ SIGINT
ถูกจัดการ ตัวแปร loop_flag
ถูกตั้งค่าเป็นศูนย์
while
loop ไม่ใช่การวนซ้ำแบบอนันต์ จะหยุดการวนซ้ำหากตัวแปร loop_flag
ถูกตั้งค่าเป็นค่าที่ไม่ใช่ศูนย์ การหมุนของ while
loop แต่ละครั้งจะใช้การ kill
เพื่อส่งสัญญาณ SIGUSR1
ไปยังสคริปต์นี้ โดยส่งไปยังรหัสกระบวนการของสคริปต์ สคริปท์ส่งสัญญาณให้ตัวเองได้!
sigusr1_handler()
เพิ่มค่าตัวแปร sigusr1_count
และส่งข้อความไปยังหน้าต่างเทอร์มินัล
ทุกครั้งที่รับสัญญาณ SIGINT
ฟังก์ชัน siguint_handler()
จะเพิ่มตัวแปร sigint_count
และสะท้อนค่าไปยังหน้าต่างเทอร์มินัล
หากตัวแปร sigint_count
เท่ากับสาม ตัวแปร loop_flag
จะถูกตั้งค่าเป็นหนึ่ง และข้อความจะถูกส่งไปยังหน้าต่างเทอร์มินัลเพื่อแจ้งให้ผู้ใช้ทราบว่ากระบวนการปิดระบบได้เริ่มต้นขึ้นแล้ว
เนื่องจาก loop_flag
ไม่เท่ากับศูนย์อีกต่อไป วง while
จะสิ้นสุดและสคริปต์จะเสร็จสิ้น แต่การกระทำนั้นจะเพิ่มสัญญาณ EXIT
โดยอัตโนมัติและเรียกฟังก์ชัน exit_handler()
./triple.sh
หลังจากกด Ctrl+C สามครั้ง สคริปต์จะยุติและเรียกใช้ exit_handler()
โดยอัตโนมัติ
อ่านสัญญาณ
ด้วยการดักจับสัญญาณและจัดการกับพวกมันในฟังก์ชันตัวจัดการที่ตรงไปตรงมา คุณสามารถทำให้สคริปต์ Bash ของคุณเป็นระเบียบอยู่ด้านหลังตัวเอง แม้ว่าจะถูกยกเลิกโดยไม่คาดคิดก็ตาม ที่ช่วยให้คุณมีระบบไฟล์ที่สะอาดขึ้น นอกจากนี้ยังป้องกันความไม่เสถียรในครั้งต่อไปที่คุณเรียกใช้สคริปต์ และอาจป้องกันช่องโหว่ด้านความปลอดภัยได้ ทั้งนี้ขึ้นอยู่กับวัตถุประสงค์ของสคริปต์ของคุณ
ที่เกี่ยวข้อง: วิธีตรวจสอบความปลอดภัยของระบบ Linux ของคุณด้วย Lynis