วิธีใช้สัญญาณลินุกซ์ในสคริปต์ทุบตี

เผยแพร่แล้ว: 2022-08-09
แล็ปท็อป Linux แสดง bash prompt
fatmawati achmad zaenuri/Shutterstock.com

เคอร์เนล Linux ส่งสัญญาณไปยังกระบวนการเกี่ยวกับเหตุการณ์ที่จำเป็นต้องตอบสนอง สคริปต์ที่ประพฤติตัวดีจะจัดการกับสัญญาณได้อย่างสวยงามและแข็งแกร่ง และสามารถล้างออกได้แม้คุณจะกด Ctrl+C นี่คือวิธีการ

สัญญาณและกระบวนการ

สัญญาณเป็นข้อความทางเดียวที่สั้น รวดเร็ว ที่ส่งไปยังกระบวนการต่างๆ เช่น สคริปต์ โปรแกรม และภูต พวกเขาแจ้งให้กระบวนการทราบเกี่ยวกับสิ่งที่เกิดขึ้น ผู้ใช้อาจกด Ctrl+C หรือแอปพลิเคชันอาจพยายามเขียนไปยังหน่วยความจำที่ไม่สามารถเข้าถึงได้

หากผู้เขียนกระบวนการคาดการณ์ว่าอาจมีการส่งสัญญาณบางอย่างไป พวกเขาสามารถเขียนรูทีนลงในโปรแกรมหรือสคริปต์เพื่อจัดการกับสัญญาณนั้นได้ กิจวัตรดังกล่าวเรียกว่า ตัวจัดการสัญญาณ มันจับหรือดักสัญญาณ และดำเนินการบางอย่างเพื่อตอบสนองต่อสัญญาณนั้น

วิธีจัดการกระบวนการจาก Linux Terminal: 10 คำสั่งที่คุณต้องรู้
ที่เกี่ยวข้อง วิธีจัดการกระบวนการจาก Linux Terminal: 10 คำสั่งที่คุณต้องรู้

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

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

นี่คือวิธีที่คุณสามารถจัดการกับสัญญาณในสคริปต์ของคุณเองได้

พบกับสัญญาณ

คำสั่ง Linux บางคำสั่งมีชื่อที่คลุมเครือ ไม่ใช่คำสั่งดักจับสัญญาณ เรียกว่า trap เรายังสามารถใช้ trap ด้วยตัวเลือก -l (รายการ) เพื่อแสดงรายการสัญญาณทั้งหมดที่ลีนุกซ์ใช้

 กับดัก -l 

แสดงรายการสัญญาณใน Ubuntu ด้วย trap -l

แม้ว่ารายการลำดับเลขของเราจะสิ้นสุดที่ 64 แต่จริงๆ แล้วมี 62 สัญญาณ สัญญาณ 32 และ 33 หายไป ไม่ได้ใช้งานใน Linux ฟังก์ชันเหล่านี้ถูกแทนที่ด้วยฟังก์ชันในคอมไพเลอร์ gcc สำหรับจัดการเธรดแบบเรียลไทม์ ทุกอย่างตั้งแต่สัญญาณ 34, SIGRTMIN ไปจนถึงสัญญาณ 64, SIGRTMAX เป็นสัญญาณแบบเรียลไทม์

คุณจะเห็นรายการต่างๆ ในระบบปฏิบัติการที่คล้าย Unix ที่แตกต่างกัน ตัวอย่างเช่นใน OpenIndiana มีสัญญาณ 32 และ 33 พร้อมกับสัญญาณพิเศษจำนวนมากที่นับรวมเป็น 73

แสดงรายการสัญญาณใน OpenIndiana พร้อมกับดัก -l

สามารถอ้างอิงสัญญาณตามชื่อ หมายเลข หรือชื่อย่อได้ ชื่อย่อของพวกเขาเป็นเพียงชื่อที่มีการนำ “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 บนบรรทัดคำสั่ง

บรรทัดข้อความของเราถูกพิมพ์ทุกครั้งที่เรากด 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 

ทำให้สคริปต์สามารถเรียกใช้งานได้ด้วย chmod

ส่วนที่เหลือของสคริปต์นั้นง่ายมาก เราจำเป็นต้องทราบ ID กระบวนการของสคริปต์ ดังนั้นเราจึงมีสคริปต์ที่สะท้อนถึงเรา ตัวแปร $$ เก็บ ID กระบวนการของสคริปต์

เราสร้างตัวแปรที่เรียกว่าตัว counter และตั้งค่าให้เป็นศูนย์

วง while จะทำงานตลอดไปเว้นแต่จะถูกบังคับให้หยุด มันเพิ่มตัวแปรตัว counter สะท้อนไปที่หน้าจอ และพักสักครู่

มาเรียกใช้สคริปต์และส่งสัญญาณที่แตกต่างกันไป

 ./simple-loop.sh 

สคริปต์ที่ระบุว่าถูกยกเลิกด้วย Ctrl+C

เมื่อเรากด "Ctrl+C" ข้อความของเราจะถูกพิมพ์ไปที่หน้าต่างเทอร์มินัลและสคริปต์จะสิ้นสุดลง

เรียกใช้อีกครั้งและส่งสัญญาณ SIGQUIT โดยใช้คำสั่ง kill เราจะต้องทำสิ่งนั้นจากหน้าต่างเทอร์มินัลอื่น คุณจะต้องใช้รหัสกระบวนการที่รายงานโดยสคริปต์ของคุณเอง

 ./simple-loop.sh
 ฆ่า -SIGQUIT 4575 

สคริปต์ที่ระบุว่าถูกยกเลิกด้วย SIGQUIT

ตามที่คาดไว้สคริปต์รายงานว่าสัญญาณมาถึงแล้วจะสิ้นสุดลง และสุดท้าย เพื่อพิสูจน์ประเด็น เราจะทำอีกครั้งด้วยสัญญาณ SIGTERM

 ./simple-loop.sh
 ฆ่า -SIGTERM 4584 

สคริปต์ที่ระบุว่าถูกยกเลิกด้วย SIGTERM

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

การจัดการสัญญาณในสคริปต์

เราสามารถแทนที่สตริงการตอบกลับด้วยชื่อของฟังก์ชันในสคริปต์ของคุณได้ คำสั่ง 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 

สคริปต์ที่ใช้ SIGUSR1 ต้องการชุดค่าผสม Ctrl+C สามชุดเพื่อปิด และจับสัญญาณ EXIT เมื่อปิด

หลังจากกด Ctrl+C สามครั้ง สคริปต์จะยุติและเรียกใช้ exit_handler() โดยอัตโนมัติ

อ่านสัญญาณ

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

ที่เกี่ยวข้อง: วิธีตรวจสอบความปลอดภัยของระบบ Linux ของคุณด้วย Lynis