Membungkus logika berulang menjadi blok yang bisa dipanggil ulang dengan functions: sintaks deklarasi, lingkup variabel global vs `local`, argumen `$1 $2`, dan nilai balik via `return` serta `echo` + command substitution. Dilengkapi praktik membangun pustaka fungsi log, cek file, dan verifikasi root beserta jebakan yang sering menjebak.

Setelah di episode 12 sebelumnya kita menguasai perulangan while dan until serta membaca file baris demi baris, pada episode kali ini kita mengangkat satu masalah yang pasti mulai kalian rasakan: kode yang semakin panjang dan berulang. Pola yang sama — menulis log, memeriksa apakah sebuah file ada, memvalidasi hak akses root — mulai muncul berulang-ulang di berbagai bagian script.
Di sinilah functions berperan. Bayangkan sebuah dapur profesional: setiap juru masak tidak menulis ulang resep dari nol setiap kali ada pesanan masuk. Resepnya disimpan sekali, dan dipanggil namanya — "ayam bakar" — kapan pun dibutuhkan. Functions adalah resep-resep itu: kalian menulis logika sekali, memberi nama, lalu memanggilnya sesering mungkin dari mana pun.
Functions mengubah script dari sekadar daftar perintah linier menjadi arsitektur yang terstruktur: logika dibungkus, diberi nama, diuji sekali, dan dipakai di banyak tempat. Ini prinsip DRY (Don't Repeat Yourself) yang sama yang berlaku di semua bahasa pemrograman. Pada episode ini kita akan membedah sintaks fungsi Bash, lingkup variabel, cara mengirim dan mengambil nilai, hingga membangun pustaka fungsi yang bisa dipakai ulang antar script.
Sebelum membahas sintaks, mari kita pahami masalah apa yang dipecahkan functions. Perhatikan tiga keuntungan utamanya:
do_a_lot_of_stuff() menjadi nama yang menjelaskan dirinya sendiri, sehingga alur utama script jadi ringkas.Tanpa functions, script kalian akan terus menumpuk duplikasi seperti fotokopi yang menumpuk di meja kerja — satu perubahan pada aturan log berarti mencari dan memperbaiki semua fotokopi. Dengan functions, satu perbaikan di sumber langsung memperbaiki semua pemanggil.
nama() { ... }Ada dua gaya penulisan fungsi di Bash — gaya POSIX nama() dan gaya dengan kata kunci function:
sapa() {
echo "Halo, selamat datang!"
}
function ucap_terima_kasih {
echo "Terima kasih sudah datang!"
}Untuk proyek yang harus portabel antar shell, gaya nama() adalah pilihan teraman. Gaya function adalah ekstensi Bash yang tidak ada di POSIX sh (ingat: kalian bisa saja mengetik bash script.sh, tapi script yang menargetkan sh harus menghindarinya).
Memanggil fungsi semudah menulis namanya — tanpa tanda kurung:
sapa() {
echo "Halo, selamat datang!"
}
sapa
sapa
sapa
# Output: tiga kali "Halo, selamat datang!"Dua catatan penting yang membedakan Bash dari kebanyakan bahasa lain:
() — sapa() hanya untuk mendeklarasikan, bukan memanggil. Memanggil cukup sapa.sapa dipanggil sebelum deklarasi sapa(), Bash akan mencari perintah bernama sapa — dan karena tidak ada, muncul error command not found.localIni salah satu perbedaan paling penting antara Bash dan bahasa modern: variabel di Bash bersifat global secara default — bahkan di dalam fungsi. Variabel yang diubah di dalam fungsi tetap mengubah nilainya setelah fungsi selesai:
warna="biru"
catat_warna() {
warna="merah"
echo "Di dalam fungsi: $warna"
}
catat_warna
echo "Di luar fungsi : $warna"
# Di dalam fungsi: merah
# Di luar fungsi : merah <- nilainya ikut berubah!Ini adalah efek samping yang sering tidak diinginkan — fungsi yang tidak sengaja menimpa variabel global bisa merusak state script secara misterius. Solusinya: deklarasikan variabel yang hanya dipakai di dalam fungsi dengan kata kunci local:
warna="biru"
catat_warna() {
local warna="merah"
echo "Di dalam fungsi: $warna"
}
catat_warna
echo "Di luar fungsi : $warna"
# Di dalam fungsi: merah
# Di luar fungsi : biru <- aman!local membuat variabel itu hidup hanya selama fungsi berjalan dan menghilang setelahnya — seperti catatan tempel yang dibuang begitu tugas selesai, tidak meninggalkan bekas di meja kerja.
Important
Selalu gunakan local untuk variabel yang hanya dibutuhkan di dalam fungsi. Ini adalah disiplin yang memisahkan penulis script amatir dari yang profesional. Variabel global di luar fungsi tetap boleh — untuk konfigurasi bersama seperti LOG_DIR atau TIMEOUT — tetapi jangan pernah membiarkan fungsi mencemari variabel yang tidak disengaja. Aturannya: defaultkan local, global hanya untuk yang memang dibagi.
$1, $2, dst.Fungsi yang kaku — selalu melakukan hal yang sama — baru setengah berguna. Agar fleksibel, fungsi perlu menerima masukan. Caranya: berikan argumen saat memanggil, dan baca di dalam fungsi lewat $1, $2, $3:
tambah() {
local a="$1"
local b="$2"
local hasil=$((a + b))
echo "Hasil: $a + $b = $hasil"
}
tambah 5 3
tambah 100 200Perhatikan dua hal penting:
$1, $2 di dalam fungsi mengacu pada argumen fungsi, bukan argumen script. Ini kesalahan yang sangat umum: pemula membaca $1 di dalam fungsi lalu mengira itu argumen pertama script. Faktanya, saat fungsi dipanggil, $1 sementara diganti dengan argumen pertama fungsi — dan pulih kembali setelah fungsi selesai. Inilah alasan mengapa variabel-variabel ini aman dipakai di dalam fungsi tanpa local.$1 sampai $9, lalu $10, $11, dan seterusnya — dan "$@" di dalam fungsi menampung semua argumen fungsi sebagai daftar terpisah.Tip
Untuk fungsi yang menerima banyak argumen, segera salin ke variabel bernama di awal: local src="$1"; local dst="$2". Ini mengubah $1, $2 yang misterius menjadi src dan dst yang menjelaskan diri sendiri — dan menjaga argumen asli tidak berubah jika ada pemanggilan fungsi lain di tengah jalan. Fungsi yang baik adalah fungsi yang 5 baris pertamanya mendokumentasikan apa yang diterimanya.
return vs Mengembalikan StringFungsi di Bash bisa "memberi hasil kembali" lewat dua cara yang sangat berbeda, dan memahami bedanya akan menyelamatkan kalian dari bug yang membingungkan.
return mengembalikan exit status — bukan nilai. Ia menerima angka 0–255, dan angka itu menjadi exit status fungsi yang bisa dibaca dengan $? atau diuji dalam kondisi:
cek_file() {
if [ -f "$1" ]; then
return 0
fi
return 1
}
if cek_file "/etc/hostname"; then
echo "File ditemukan."
else
echo "File tidak ditemukan."
fireturn 0 menandakan sukses, return dengan nilai lain (biasanya 1) menandakan gagal. Inilah mengapa fungsi bisa langsung dipakai di dalam if — Bash menguji exit status-nya.
Untuk mengembalikan data (string), gunakan echo + command substitution:
get_ext() {
local file="$1"
echo "${file##*.}"
}
ekstensi=$(get_ext "laporan.pdf")
echo "Ekstensi: $ekstensi"
# Ekstensi: pdfDi sini get_ext tidak "mengembalikan" apa pun secara formal — ia mencetak ke stdout, dan $( ... ) (command substitution, sudah kita bahas di episode tentang ekspansi) menangkap semua output itu sebagai string.
Warning
Jangan pernah mencoba mengembalikan string lewat return — return "halo" akan error (numeric argument required) karena return hanya menerima angka exit status. Dan jangan mencampur dua gaya: jika sebuah fungsi harus mengembalikan string, jangan tambahkan echo pembantu di tengah jalan, karena semua echo akan ikut tertangkap oleh command substitution. Pilih satu: fungsi itu pengembali status (return), atau pengembali data (echo), jangan keduanya campur.
Sekarang kita rangkai semuanya dalam studi kasus yang sangat nyata: sebuah pustaka fungsi (lib.sh) yang bisa di-source dari script mana pun. Pustaka berisi tiga fungsi yang selalu dibutuhkan script produksi: menulis log dengan timestamp, memeriksa keberadaan file, dan memverifikasi hak akses root:
#!/usr/bin/env bash
log_info() {
local msg="$1"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO : $msg"
}
log_error() {
local msg="$1"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR : $msg" >&2
}
is_file() {
[ -f "$1" ]
}
check_root() {
if [ "$(id -u)" -ne 0 ]; then
log_error "Script harus dijalankan sebagai root."
return 1
fi
return 0
}Cara memakai pustaka ini dari script utama adalah dengan source (alias .), lalu panggil fungsinya seolah-olah didefinisikan di tempat:
#!/usr/bin/env bash
source "$(dirname "$0")/lib.sh"
if ! check_root; then
exit 1
fi
if is_file "/etc/hostname"; then
log_info "/etc/hostname ada, lanjut konfigurasi."
else
log_error "/etc/hostname tidak ditemukan."
exit 1
fiPerhatikan pola-pola yang telah kita pelajari bersatu di sini:
check_root memakai $() (command substitution) untuk mengambil output id -u, lalu membandingkannya dengan 0.is_file adalah fungsi penguji — ia mengeksekusi [ -f "$1" ] dan exit status test itulah yang menjadi exit status fungsi. Ringkas dan idiomatis.log_error menulis ke stderr (>&2) — keputusan penting: log yang salah dibedakan dari log normal sehingga bisa dipisahkan dalam pipeline dan file log.source "$(dirname "$0")/lib.sh" memuat pustaka dari direktori script yang sama, tidak peduli dari mana script dipanggil.| Kesalahan | Gejala | Solusi |
|---|---|---|
| Memanggil fungsi sebelum dideklarasikan | command not found | Deklarasikan fungsi di atas pemanggilannya |
Lupa local | Variabel fungsi mencemari global | local untuk semua variabel internal |
return "string" untuk data | numeric argument required | return hanya angka status; pakai echo untuk string |
Memakai $1 script di dalam fungsi | Argumen salah / tak terduga | $1 di fungsi adalah argumen fungsi, salin ke nama jelas |
function di script target sh | Error di shell lain | Pakai gaya nama() |
| Fungsi memakai variabel global tak terduga | Bug samar yang sulit dilacak | Cek semua variabel fungsi sudah local |
Note
Ada satu jebakan scoping lain yang sering dianggap mistis: fungsi yang dipanggil di dalam pipeline juga berjalan di subshell (ingat episode 12). Jika sebuah fungsi mengubah variabel global, lalu dipanggil dari dalam cat file | my_func, perubahannya hilang begitu pipeline selesai. Ini perilaku subshell yang sama — bukan bug Bash. Untuk fungsi yang harus mengubah state global, panggil di luar pipeline atau gunakan teknik yang sama seperti solusi subshell di episode 12.
Pada episode 13 ini kita telah mengubah cara kalian menulis script: dari daftar perintah linier menjadi arsitektur berbasis fungsi. Kita membahas sintaks nama() { ... }, alasan mengapa fungsi itu penting (reuse, readability, testability), lingkup variabel dengan local yang wajib untuk variabel internal, pengiriman argumen lewat $1 $2, serta dua cara "mengembalikan nilai" — return untuk exit status dan echo + command substitution untuk string. Semuanya dirangkai dalam pustaka fungsi lib.sh yang siap di-source di script mana pun.
Prinsip yang harus kalian bawa pulang: fungsi yang baik itu kecil, ber-nama jelas, semua variabelnya local, dan hanya punya satu cara mengembalikan hasil. Jika sebuah fungsi sudah lebih dari 40 baris atau mengerjakan tiga hal sekaligus, pecahlah.
Setelah bisa membungkus logika, ada satu bahan yang masih kurang untuk automation tingkat lanjut: menyimpan banyak data dalam satu variabel. Di episode 14 selanjutnya kita akan membahas indexed array — cara menyimpan daftar nilai dalam satu variabel, mengaksesnya dengan indeks, mengiterasi seluruh elemen, menambahkan, memotong, hingga membangun daftar dari output perintah. Di sanalah daftar server, daftar file backup, dan koleksi data lain akan terasa sangat ringan ditangani. Sampai jumpa di episode berikutnya!