Skip to content

4. Signallar va traps โ€‹

๐ŸŽฏ Bu bobda nimani o'rganasiz:

  • Unix signallari โ€” SIGINT, SIGTERM, SIGKILL, SIGHUP va boshqalar
  • trap orqali signallarni qabul qilish
  • EXIT pseudo-signali โ€” eng muhim cleanup mexanizmi
  • Cleanup pattern โ€” temp fayllar, lock fayllar, ulanishlar
  • Graceful shutdown โ€” Ctrl+C bosilsa ham toza yakunlanish
  • Background jarayonlar โ€” &, wait, signal propagation

โฑ Vaqt: ~25 daqiqa ๐Ÿงช Mashqlar: bashlings watch 09_traps (kelajak sprint)


4.1. Nima uchun bu kerak? โ€‹

Quyidagi vaziyatlarni tasavvur qiling:

  1. Skriptingiz 100MB vaqtinchalik faylga ma'lumot yozayotgan edi. Foydalanuvchi Ctrl+C bosdi. Fayl qaytib qoldi โ€” diskni iflos qildi.

  2. Database backup skripti ishlayotgan edi. Terminal yopildi. Yarim-bajarilgan backup va ochiq ulanish saqlanib qoldi.

  3. Server-monitoring skripti cron'da yurar edi. Tizim qayta yuklandi. PID fayli qoldi, keyingi run "allaqachon ishlamoqda" deb noto'g'ri xato berdi.

Bularning hammasi trap va signallar bilan oldini olinadi.

Asosiy g'oya

Hech qachon "muvaffaqiyatli tugadi" deb umid qilmang. Skriptingiz istalgan vaqtda to'xtatilishi mumkin โ€” Ctrl+C, terminal yopilishi, OOM killer, kill -9. Cleanup'ni kafolatlash kerak.


4.2. Unix signallari โ€” qisqacha modeli โ€‹

Signal โ€” bir jarayondan boshqasiga (yoki kernel'dan) asinxron xabar. Foydalanuvchining Ctrl+C โ€” bu signal. kill buyrug'i โ€” signal yuborish. Tizim ishini boshqarish โ€” signalar orqali.

Signallar โ€” har biri raqam va nomga ega โ€‹

bash
kill -l
# 1) SIGHUP   2) SIGINT   3) SIGQUIT  ...
# 9) SIGKILL  15) SIGTERM ...

Kerak bo'ladigan signallar โ€‹

SignalRaqamMazmuniTrap qilsa bo'ladimi?
SIGINT2Ctrl+C โ€” odob bilan to'xtatish so'roviโœ…
SIGTERM15"Iltimos to'xtang" (default kill PID)โœ…
SIGHUP1Terminal yopildi (hangup)โœ…
SIGQUIT3Ctrl+\ โ€” core dump bilan to'xtatishโœ…
SIGUSR110/30Foydalanuvchi belgilangan #1โœ…
SIGUSR212/31Foydalanuvchi belgilangan #2โœ…
SIGCHLD17/20Child process holati o'zgardiโœ…
SIGPIPE13Yozilayotgan pipe yopildiโœ…
SIGALRM14Timer ishladiโœ…
SIGKILL9Darhol o'ldirish โ€” qarshi tura olmaysizโŒ YO'Q
SIGSTOP19/17Darhol pauza โ€” qarshi tura olmaysizโŒ YO'Q
EXIT(0)Bash pseudo-signali โ€” har xil yakundaโœ… (eng muhim)

SIGKILL ni hech qachon ushlab bo'lmaydi

kill -9 PID โ€” jarayonni darhol o'ldiradi. Cleanup kodi ishlamaydi. Shuning uchun kritik holatlar uchun "tashqi qo'riqchi" jarayonlar kerak.


4.3. kill โ€” signal yuborish โ€‹

bash
# Default โ€” SIGTERM (15) yuboradi
kill 1234

# Aniq signal
kill -SIGINT 1234
kill -INT 1234            # SIG prefiksini tashlash mumkin
kill -2 1234              # raqam bilan

# Eng kuchlisi โ€” qarshi tura olmaydi
kill -9 1234
kill -KILL 1234

# Nom bo'yicha (process name)
killall my-script.sh
pkill -f "long pattern"

# Joriy jarayon PID si
echo $$                   # bash uchun

kill -0 โ€” faqat tekshirish โ€‹

bash
# Jarayon hayotmi?
if kill -0 1234 2>/dev/null; then
    echo "Hali ishlayapti"
fi

-0 signal yuborilmaydi, faqat ruxsat tekshiriladi.


4.4. trap asoslari โ€‹

trap โ€” signal qabul qilinganda nima qilishni belgilaydi.

Sintaksis โ€‹

bash
trap '<bajariladigan kod>' SIGNAL [SIGNAL ...]

Birinchi misol โ€‹

bash
#!/usr/bin/env bash

trap 'echo "โš  Ctrl+C bosildi, lekin men tirikman!"' SIGINT

echo "5 soniya kutaman..."
for i in {1..5}; do
    echo "  $i"
    sleep 1
done
echo "Yakunlandi"

Ishga tushiring va Ctrl+C bosing โ€” skript ogohlantirib davom etadi.

Trap'larni ko'rish โ€‹

bash
trap -p                   # joriy o'rnatilgan trap'lar ro'yxati
trap -p SIGINT            # faqat SIGINT uchun

Trap'ni o'chirish (default'ga qaytarish) โ€‹

bash
trap - SIGINT             # SIGINT default xulq-atvor (kill)
trap - EXIT INT TERM      # bir nechta birgalikda

Trap'ni butunlay e'tibor bermaslik โ€‹

bash
trap '' SIGINT            # Ctrl+C umuman ishlamaydi

E'tiborsiz qoldirish xavfli

trap '' SIGINT โ€” foydalanuvchining Ctrl+C ni bekor qiladi. Bu shubhali UX. Faqat maxsus, hujjatlangan vaziyatlarda ishlating.


4.5. EXIT โ€” eng muhim pseudo-signal โ€‹

EXIT โ€” haqiqiy Unix signali emas, bash ning maxsus pseudo-signali. U har qanday yo'l bilan skript tugaganda ishlaydi:

  • Normal yakun (oxirgi buyruq bajarildi)
  • exit buyrug'i chaqirildi
  • Xato bilan to'xtadi (set -e ostida)
  • Signal qabul qilindi (SIGINT, SIGTERM)

Cleanup uchun zarba

Aynan shu xususiyat EXIT ni cleanup uchun mukammal joy qiladi. Bir marta yozasiz โ€” har holatda ishlaydi.

Klassik pattern โ€‹

bash
#!/usr/bin/env bash
set -euo pipefail

tmpfile=$(mktemp)

cleanup() {
    rm -f "$tmpfile"
    echo "๐Ÿงน Tozalandi"
}

trap cleanup EXIT

# Asosiy ish
echo "ma'lumot" > "$tmpfile"
# ... boshqa kod ...

# `cleanup` har holda chaqiriladi

Sinab ko'ring:

  • Normal tugating โ†’ cleanup chiqaradi
  • Ctrl+C bosing โ†’ cleanup chiqaradi
  • exit 1 qo'shing โ†’ cleanup chiqaradi

4.6. Eng yaxshi cleanup pattern โ€‹

Ko'pchilik skriptlar Ctrl+C ni alohida xabar bilan qarshilab, EXIT ni cleanup uchun ishlatadi. Bu eng toza model:

bash
#!/usr/bin/env bash
set -euo pipefail

tmpfile=$(mktemp)
lockfile="/tmp/myapp.lock"

cleanup() {
    local rc=$?
    rm -f "$tmpfile"
    rm -f "$lockfile"
    if [[ $rc -ne 0 ]]; then
        echo "โŒ Skript $rc kodi bilan tugadi"
    fi
}

# 1) EXIT โ€” har holatda cleanup
trap cleanup EXIT

# 2) INT / TERM โ€” graceful exit (EXIT trap ham chaqiriladi)
trap 'echo "โš  to`xtatish so`rovi qabul qilindi"; exit 130' INT TERM

# Asosiy ish
echo $$ > "$lockfile"
echo "ma'lumot" > "$tmpfile"

for i in {1..30}; do
    echo "Qadam $i"
    sleep 1
done

echo "โœ… Yakunlandi"

Nima ishlaydi? โ€‹

HodisaINT trapEXIT trapCleanup chaqiriladi?
Normal yakunโ€”โœ…Ha
Ctrl+Cโœ… โ†’ exit 130โœ…Ha
kill PIDTERM โ†’ exit 130โœ…Ha
kill -9 PIDโŒ ushlamaydiโŒYo'q โš 
Skript ichida exit 1โ€”โœ…Ha

Exit kodlar konventsiyasi

  • 130 = 128 + SIGINT(2) โ€” Ctrl+C natijasi
  • 143 = 128 + SIGTERM(15) โ€” kill natijasi

Bu Unix konvensiyasi. Programatik tekshirishda foydali.


4.7. Bir nechta cleanup va done flag โ€‹

Agar cleanup ikki marta chaqirilishidan saqlanish kerak bo'lsa (masalan, Ctrl+C keyin exit 1 ham chaqirilsa):

bash
__cleaned=0
cleanup() {
    [[ $__cleaned -eq 1 ]] && return
    __cleaned=1

    rm -f "$tmpfile"
    rm -f "$lockfile"
}
trap cleanup EXIT

Bu โ€” idempotent cleanup patterni.


4.8. SIGKILL โ€” qarshi tura olmaslik โ€‹

bash
trap 'echo "haa"' SIGKILL   # โŒ bash xato beradi

Bash hatto bunday yozishga ruxsat bermaydi. Sababi: SIGKILL va SIGSTOP โ€” kernel darajasidagi signallar, foydalanuvchi jarayoni hech qanday yo'l bilan to'sib qo'ya olmaydi.

Real qoidalar โ€‹

  • kill PID (SIGTERM) โ€” odobli so'rov. Skript cleanup qilib chiqishi mumkin.
  • kill -9 PID (SIGKILL) โ€” kuch ishlatish. Cleanup ishlamaydi.

Production xulq-atvor

  1. Birinchi yondashuv: odobli SIGTERM
  2. Bir nechta soniya kutish (cleanup uchun)
  3. Hech narsa bo'lmasa: SIGKILL (oxirgi chora)

systemctl stop aniq shu modelda ishlaydi.


4.9. Background jarayonlar va wait โ€‹

Asinxron ishlovchi skriptlarda โ€” & bilan ishga tushirilgan background jarayonlar โ€” alohida e'tibor kerak.

& va $! โ€‹

bash
sleep 30 &
echo "Background PID: $!"     # eng oxirgi background PID

wait โ€” kutish โ€‹

bash
sleep 5 &
pid1=$!

sleep 3 &
pid2=$!

wait $pid1 $pid2
echo "Ikkalasi ham tugadi"

Signal propagation โ€‹

Default'da, agar parent skriptga Ctrl+C bosilsa โ€” child jarayonlar avtomatik o'lmaydi. Aniq yo'naltirish kerak:

bash
#!/usr/bin/env bash

cleanup() {
    echo "Child'larni o'ldiraman..."
    jobs -p | xargs -r kill 2>/dev/null
}
trap cleanup EXIT INT TERM

# Bir nechta worker
worker.sh &
worker.sh &
worker.sh &

wait

jobs -p โ€” barcha background PID'lar. xargs -r kill โ€” ularni o'ldirish.

wait -n (Bash 4.3+) โ€‹

Birinchi tugagan child'ni kutish:

bash
worker1 &
worker2 &
worker3 &

# Birortasi tugashini kutamiz
wait -n
echo "Bittasi tugadi"

4.10. Graceful shutdown patterni โ€‹

Uzun ishlovchi skriptlar uchun (server, monitor, daemon) โ€” to'xtash so'rovini ravon qabul qilish kerak.

bash
#!/usr/bin/env bash
#
# monitor.sh โ€” har 5 soniyada disk holatini tekshirish
#

set -euo pipefail

running=1
shutdown() {
    echo "Shutdown so'rovi qabul qilindi, joriy iteratsiyani yakunlayman..."
    running=0
}
trap shutdown INT TERM

while [[ $running -eq 1 ]]; do
    df -h / | tail -1
    sleep 5
done

echo "โœ… Toza yakunlanish"

Bu nima qiladi? โ€‹

  1. Ctrl+C bosilsa โ€” running=0 qiladi
  2. Joriy iteratsiya tugaydi (df chiqishi to'liq)
  3. Keyingi while tekshiruvi false qaytaradi
  4. Skript toza yakunlanadi

exit chaqirilmaydi โ€” har ishni tugatib chiqamiz.


4.11. Debug uchun ERR va DEBUG traplari โ€‹

ERR โ€” har xatoda โ€‹

bash
#!/usr/bin/env bash
set -e

trap 'echo "โŒ Xato $LINENO qatorida: $BASH_COMMAND"' ERR

echo "Ish boshlandi"
ls /yoq-katalog            # bu yerda xato bo'ladi
echo "Bu satrgacha yetmaydi"

Ishga tushganda:

text
Ish boshlandi
ls: cannot access '/yoq-katalog': No such file or directory
โŒ Xato 6 qatorida: ls /yoq-katalog

DEBUG โ€” har buyruqdan oldin โ€‹

bash
trap 'echo ">> [LINE $LINENO] $BASH_COMMAND"' DEBUG

x=5
echo "salom"
ls

Natija:

text
>> [LINE 3] x=5
>> [LINE 4] echo "salom"
salom
>> [LINE 5] ls
...

set -x bilan farqi

set -x ham har buyruqni chiqaradi, lekin DEBUG trap siz xohlagan ko'rinishda formatlash imkonini beradi.


4.12. Real misol โ€” robust backup skript โ€‹

Hamma kontseptsiyalarni birlashtirgan to'liq misol:

bash
#!/usr/bin/env bash
#
# backup.sh โ€” Ctrl+C ham, kill ham, oddiy yakun ham โ€” barchasini toza boshqaradi
#

set -euo pipefail

readonly SCRIPT_NAME="$(basename "$0")"
readonly LOCKFILE="/tmp/${SCRIPT_NAME%.sh}.lock"
TMPDIR=""

# --- Lock check (bitta nusxa pattern) ---
if [[ -f "$LOCKFILE" ]]; then
    pid=$(cat "$LOCKFILE")
    if kill -0 "$pid" 2>/dev/null; then
        echo "โŒ Backup allaqachon ishlamoqda (PID: $pid)" >&2
        exit 1
    else
        echo "โš  Eski stale lock topildi, tozalanmoqda"
        rm -f "$LOCKFILE"
    fi
fi

echo $$ > "$LOCKFILE"

# --- Cleanup funksiyasi ---
__cleaned=0
cleanup() {
    local rc=$?
    [[ $__cleaned -eq 1 ]] && return
    __cleaned=1

    echo ""
    echo "๐Ÿงน Tozalanmoqda..."
    [[ -n "$TMPDIR" && -d "$TMPDIR" ]] && rm -rf "$TMPDIR"
    rm -f "$LOCKFILE"

    if [[ $rc -eq 0 ]]; then
        echo "โœ… Muvaffaqiyatli yakunlandi"
    elif [[ $rc -eq 130 ]]; then
        echo "โš  Foydalanuvchi to'xtatdi (Ctrl+C)"
    else
        echo "โŒ Xato bilan tugadi (exit=$rc)"
    fi
}

trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM

# --- Asosiy ish ---
TMPDIR=$(mktemp -d)
echo "๐Ÿ“ Vaqtinchalik katalog: $TMPDIR"

echo "๐Ÿ“ฆ Backup boshlandi ($(date '+%T'))"

for i in {1..10}; do
    echo "  Qadam $i / 10"
    sleep 1
done

archive="$HOME/backup_$(date +%Y%m%d_%H%M%S).tar.gz"
echo "๐Ÿ’พ Arxivga yozilmoqda: $archive"
# tar -czf "$archive" -C "$TMPDIR" .   # haqiqiy buyruq

echo "๐Ÿ“Š Hajmi: $(du -sh "$TMPDIR" 2>/dev/null | cut -f1)"

Bu skript:

  • Lock fayl orqali bitta nusxa kafolatlaydi (kill -0 bilan stale tekshiradi)
  • trap cleanup EXIT โ€” har qanday yakunda tozalanadi
  • INT โ†’ exit 130 va TERM โ†’ exit 143 โ€” Unix konventsiya
  • __cleaned flag โ€” ikki marta chaqirilishni oldini oladi
  • Vaqtinchalik katalog mktemp -d โ€” xavfsiz va noyob
  • Color/emoji โ€” foydalanuvchiga aniq feedback

4.13. Tez-tez uchraydigan xatolar โ€‹

Klassik tuzoqlar

  1. SIGKILL ni trap qilishga urinish. Imkonsiz. kill -9 cleanup'ni chetlab o'tadi. Tashqi qo'riqchi (systemd, supervisord) ishlating.

  2. Cleanup'da yangi xato chiqarish.set -e ostida cleanup ichidagi xato keyingi qatorlarni tushiradi. cleanup() { rm -f "$tmpfile" || true; ... }.

  3. EXIT trap ko'rsatkichlari.cleanup chaqirilganda $? โ€” eng oxirgi buyruq exit code. Trap birinchi qatorida local rc=$? qilib ushlang.

  4. Background process'lar trap'siz qoldirilgan. Parent o'lganda children orfan bo'lib qoladi. trap 'kill $(jobs -p) 2>/dev/null' EXIT bilan saqlang.

  5. Trap'da o'zgaruvchi ishlatish โ€” single vs double quote.

    bash
    trap "echo $tmpfile" EXIT   # โŒ trap O'RNATILGAN paytda almashtiriladi
    trap 'echo $tmpfile' EXIT   # โœ“ trap CHAQIRILGAN paytda

    Bu nozik farq! cleanup funksiya orqali yozish xavfsizroq.

  6. exit ni cleanup ichida unutish.

    bash
    cleanup() { rm -f "$tmp"; }     # OK โ€” EXIT trap holatida
    trap 'cleanup; exit 130' INT    # โœ“ exit ham kerak, aks holda davom etadi
  7. Lock fayl tozalanmagan stale holatda. Skript crash bo'lsa, lock qoladi. Har doim kill -0 $pid bilan tekshiring va kerak bo'lsa olib tashlang.


4.14. Mashqlar โ€‹

๐Ÿงช Kelajakda bashlings watch 09_traps paketida.

  1. Countdown โ€” 10 dan 1 gacha har soniyada chiqaruvchi skript yozing. Ctrl+C bosilsa "uzr, davom eta olmadi" deb chiqarsin va exit 130 qaytarsin.

  2. tmp-safe โ€” mktemp orqali fayl yaratadi, ichiga 5 ta tasodifiy son yozadi, har holatda o'chirib tashlaydi. Cleanup EXIT trap orqali bo'lsin.

  3. Single instance โ€” lock fayl orqali ikkinchi nusxa ishga tushishini taqiqlovchi skript. Stale lock'ni avtomatik aniqlasin.

  4. Parallel workers โ€” 3 ta background worker ishga tushiruvchi parent skript. Ctrl+C da hammasini o'ldirib chiqsin (jobs -p | xargs kill).

  5. ERR trace โ€” set -e va trap '... $LINENO $BASH_COMMAND ...' ERR ishlatib, xato sodir bo'lganda qator raqami va buyruqni chiqaruvchi skript yozing.


4.15. Xulosa โ€‹

TushunchaAsosiy nuqta
Signal nimaJarayonlar o'rtasidagi asinxron xabar
SIGINT (2)Ctrl+C โ€” eng ko'p uchraydigan
SIGTERM (15)Default kill โ€” odob bilan to'xtatish so'rovi
SIGKILL (9)Trap qilib bo'lmaydi โ€” cleanup ham yo'q
EXITBash pseudo-signal โ€” eng muhim cleanup nuqtasi
trap kod SIGNALSignalga reaksiya o'rnatish
trap - SIGNALDefault'ga qaytarish
trap '' SIGNALE'tibor bermay qo'yish
trap -pJoriy trap'lar ro'yxati
Exit code 130Ctrl+C natijasi (128+SIGINT)
Exit code 143kill natijasi (128+SIGTERM)
kill -0 PIDJarayon tirikligini tekshirish
$!Eng oxirgi background PID

5 ta asosiy g'oya โ€‹

  1. Hech qachon "muvaffaqiyatli" deb umid qilmang. Cleanup'ni trap EXIT bilan kafolatlang.
  2. EXIT pseudo-signal โ€” bash dunyosining eng kuchli xususiyatlaridan biri. Foydalaning.
  3. SIGKILL ushlab bo'lmaydi. Kritik holatlar uchun tashqi qo'riqchi kerak.
  4. Single instance + lock + kill -0 โ€” production skriptlarning standart pattern.
  5. Graceful shutdown โ€” while [[ $running -eq 1 ]] modeli bilan har iteratsiyani tugatib chiqing.

๐ŸŽ‰ Endi sizning skriptlaringiz har qanday vaziyatda toza yakunlanadi. Keyingi va oxirgi bobda biz set -euo pipefail, ShellCheck va getopts orqali production-grade skript yozishni o'rganamiz.

Keyingi sahifa: 5. Robust skriptlar โ†’

MIT litsenziyasi asosida tarqatiladi.