Saat kita menekan Ctrl+C, tidak ada tombol ajaib yang menghentikan program — kernel mengirim pesan pendek bernama sinyal (signal) ke proses yang sedang berjalan. Sinyal bisa datang dari kombinasi tombol, perintah kill, atau sistem ketika terminal ditutup. Skrip kita tidak kebal; dihentikan di tengah pekerjaan, skrip bisa meninggalkan file sementara dan pekerjaan setengah jadi. Chapter ini membahas sinyal yang paling sering kita temui, cara mengirim dan menerimanya, serta trap agar skrip berhenti dengan bersih.

Sinyal: Pesan Pendek Antar Proses #

Sinyal adalah notifikasi bernomor dari kernel ke sebuah proses; proses tersebut bereaksi sesuai aksi bawaan tiap sinyal — umumnya mengakhiri diri. Lima yang paling relevan bagi skrip:

NamaNomorPemicu umumAksi bawaan
SIGHUP1terminal ditutupmengakhiri proses
SIGINT2Ctrl+Cmengakhiri proses
SIGKILL9kill -9mengakhiri seketika, tak bisa ditangkap
SIGTERM15kill tanpa opsimengakhiri proses
SIGUSR110ditentukan aplikasimengakhiri proses

Builtin kill di bash bisa menerjemahkan nama dan nomor sinyal dua arah lewat opsi -l:

bash
kill -l 2
kill -l 9
kill -l 15
kill -l TERM

Output yang Dihasilkan:

text
INT
KILL
TERM
15

Tiga baris pertama menerjemahkan nomor menjadi nama; baris terakhir sebaliknya. Daftar lengkap beserta nomornya ada di signal(7) .

Mengirim Sinyal dengan kill #

Perintah kill pid mengirim SIGTERM — sama dengan kill -TERM pid atau kill -15 pid. Untuk proses yang kita mulai sendiri di background, PID-nya tersimpan di variabel khusus $!. Pola lengkapnya: jalankan, simpan PID, kirim sinyal, tunggu:

bash
sleep 30 &
pid=$!
kill -TERM $pid
wait $pid
echo "wait melaporkan: $?"

Output yang Dihasilkan:

text
wait melaporkan: 143

Mari kita bedah baris demi baris:

  1. Baris 1 (sleep 30 &): menjalankan sleep di background.
  2. Baris 2 (pid=$!): menyimpan PID proses background terakhir; nilainya beda tiap eksekusi sehingga ditampung, bukan dicetak.
  3. Baris 3 (kill -TERM $pid): mengirim SIGTERM kepada sleep.
  4. Baris 4-5: wait menunggu proses itu selesai, lalu $? melaporkan 143 — pola 128 + 15: proses yang mati karena sinyal N dilaporkan dengan kode 128 + N.

trap: Pembersihan yang Pasti Dijalankan #

trap memasang aksi untuk daftar sinyal: trap 'perintah' SINYAL1 SINYAL2 — begitu sinyal itu tiba, skrip menjalankan perintahnya lalu melanjutkan. Nama spesial EXIT berjalan saat skrip berakhir: normal, error, maupun karena sinyal yang tidak dipasangi trap sendiri. Tempat terbaik membersihkan file kerja:

bash
cat > bersih.sh <<'AKHIR'
#!/bin/bash
tmp="pesan.tmp"
trap 'rm -f "$tmp"; echo "pembersihan: $tmp dihapus"' EXIT
printf 'draf pesan\n' > "$tmp"
echo "skrip bekerja, sementara memakai $tmp"
AKHIR
bash bersih.sh
ls pesan.tmp 2>&1
echo "exit code ls: $?"

Output yang Dihasilkan:

text
skrip bekerja, sementara memakai pesan.tmp
pembersihan: pesan.tmp dihapus
ls: cannot access 'pesan.tmp': No such file or directory
exit code ls: 2

Mari kita bedah isi bersih.sh baris per baris:

  1. Baris 2 (tmp="pesan.tmp"): nama file sementara disimpan ke variabel karena dipakai dua tempat.
  2. Baris 3 (trap '...' EXIT): aksinya rm -f plus echo keterangan; dipasang sebelum file dibuat agar tidak ada celah.
  3. Baris 4-5: membuat file kerja lalu bekerja dengannya.

Urutan outputnya: pesan kerja tampil dulu, lalu aksi trap begitu skrip keluar, dan baris terakhir membuktikan pesan.tmp sudah tidak ada.

Menangkap Sinyal Khusus di Dalam Skrip #

SIGUSR1 dan SIGUSR2 disediakan untuk keperluan aplikasi — misalnya mencetak kemajuan tanpa menghentikan skrip. Tanpa trap, keduanya membunuh proses. Skrip berikut mengirim USR1 ke dirinya sendiri lewat $$ yang berisi PID skrip:

bash
cat > sinyal.sh <<'AKHIR'
#!/bin/bash
trap 'echo "sinyal USR1 diterima, pekerjaan lanjut"' USR1
echo "skrip mulai"
kill -USR1 $$
echo "skrip selesai"
AKHIR
bash sinyal.sh

Output yang Dihasilkan:

text
skrip mulai
sinyal USR1 diterima, pekerjaan lanjut
skrip selesai

Baris 2 memasang trap USR1; baris 4 mengirimnya ke PID skrip sendiri. Trap mencegat, menjalankan echo, lalu eksekusi lanjut ke baris 5. Mekanisme yang sama bekerja saat Ctrl+C: terminal mengirim SIGINT ke seluruh grup proses foreground, dan trap INT mencegatnya sebelum skrip mati.

SIGKILL yang Tidak Bisa Ditawar #

Perbedaan keduanya soal kesopanan. SIGTERM adalah permintaan agar proses mengakhiri diri — boleh ditangkap, ditunda untuk membersihkan, lalu keluar. SIGKILL dieksekusi langsung oleh kernel: proses tidak diberi kesempatan apa pun — tidak sempat menyimpan file maupun menjalankan trap. Bersama SIGSTOP, keduanya satu-satunya sinyal yang tidak bisa ditangkap, diblok, maupun diabaikan.

Skrip kita kini bisa berhenti tertib dalam kondisi apa pun. Persoalan berikutnya: bagaimana menemukan letak kesalahan ketika skrip berperilaku aneh tetapi diam saja? Peralatan menyusurinya dibahas pada Chapter Debugging Shell Scripts .