Fungsi membungkus satu blok logika menjadi satu nama yang dapat dipanggil berulang kali, sama seperti perintah pada umumnya. Topik ini membahas cara mendefinisikan fungsi shell, cara argumen diteruskan ke dalamnya, dua makna nilai balik, penggunaan variabel lokal, serta pemanfaatan fungsi untuk merapikan skrip yang berisi logika yang digunakan di banyak tempat. Bagian skrip yang berulang dapat dikemas menjadi fungsi yang mudah diperiksa.
Mendefinisikan dan memanggil fungsi#
Definisi fungsi ditulis dengan nama diikuti sepasang tanda kurung dan blok perintah di antara kurung kurawal, misalnya sapa() { perintah; }. bash juga menerima kata kunci function di depan nama, tetapi bentuk dengan tanda kurung saja lebih lazim karena dipahami oleh shell POSIX sekalipun. Fungsi dipanggil dengan menuliskan namanya seperti mengetik perintah, tanpa tanda kurung.
# definisi fungsi harus berada sebelum pemanggilannya
sapa_belanja() {
printf 'Menyiapkan daftar belanja.\n'
}
sapa_belanjaMenyiapkan daftar belanja.Definisi hanya mendaftarkan nama ke dalam shell saat itu dan tidak mencetak apa pun, sehingga keluaran muncul tepat satu kali dari baris pemanggilan. Urutan baris menentukan perilakunya, karena bash membaca skrip dari atas ke bawah dan fungsi hanya dapat dipanggil setelah definisinya dibaca. Definisi yang ditulis setelah pemanggilan menyebabkan bash menganggap namanya belum ada.
Kebiasaan yang lazim adalah menyusun seluruh definisi fungsi di bagian atas skrip, tepat setelah baris shebang dan set -euo pipefail, lalu memanggilnya di bagian bawah. Susunan ini membuat bagian bawah skrip terbaca seperti urutan langkah yang singkat, sementara rinciannya tersusun rapi di atas. Nama fungsi mengikuti aturan nama variabel, yaitu huruf, angka, dan garis bawah, dan sebaiknya menggambarkan pekerjaannya, misalnya hitung_biaya atau tulis_laporan, karena fungsi dengan nama yang jelas membuat skrip panjang tetap mudah ditelusuri.
Argumen fungsi#
Fungsi menerima argumen dengan cara yang sama seperti perintah. Di dalam blok, argumen pertama tersimpan pada $1, argumen kedua pada $2, dan seterusnya, sedangkan $# berisi jumlah argumen yang masuk. Nomor argumen mengikuti panggilan fungsi yang sedang berjalan, bukan argumen skrip, sehingga setiap pemanggilan menyuplai nilainya sendiri.
# fungsi menerima argumen seperti perintah
hitung_biaya() {
printf 'Barang %s seharga Rp%d, sebanyak %d buah\n' "$1" "$2" "$3"
}
hitung_biaya "susu formula" 8500 2
hitung_biaya "telur" 2900 12Barang susu formula seharga Rp8500, sebanyak 2 buah
Barang telur seharga Rp2900, sebanyak 12 buahDua pemanggilan menghasilkan keluaran yang berbeda karena setiap pemanggilan membawa argumennya sendiri. Argumen yang ditulis dengan kutip ganda tetap utuh meskipun memuat spasi, sesuai aturan quoting. Nomor argumen juga diperbarui pada setiap pemanggilan, sehingga fungsi yang sama dapat digunakan berulang kali untuk data yang berbeda tanpa menyimpan keadaan apa pun di dalamnya. Jumlah argumen yang masuk dapat diuji dengan $# untuk melindungi fungsi dari pemanggilan yang kurang lengkap, sebagaimana ditunjukkan fungsi berikut yang menuntut dua argumen.
# jumlah argumen diperiksa dengan $#
perkenalkan() {
if [[ $# -lt 2 ]]; then
printf 'butuh nama dan kota\n'
return 1
fi
printf '%s dari %s\n' "$1" "$2"
}
perkenalkan "Ali"
perkenalkan "Ali" "Bandung"butuh nama dan kota
Ali dari BandungPemanggilan pertama hanya membawa satu argumen, sehingga pesan petunjuk dicetak dan return 1 meninggalkan kode keluar 1 sebagai tanda kegagalan. Pemanggilan kedua lengkap, sehingga blok setelah pemeriksaan dijalankan dan kode keluarnya 0. Pemeriksaan argumen seperti ini adalah bentuk paling sederhana dari penanganan error di dalam fungsi.
Nilai balik#
Nilai balik fungsi memiliki dua jalur yang berbeda. Jalur pertama adalah kode keluar melalui return, yaitu angka 0 sampai 255 yang menyatakan keberhasilan, dengan 0 sebagai tanda berhasil. Jalur kedua adalah data yang dicetak ke keluaran standar, yang ditangkap pemanggil melalui substitusi perintah. Fungsi berikut memperlihatkan jalur pertama untuk menguji keberadaan berkas.
# siapkan berkas untuk pengujian keberadaan
printf 'catatan belanja minggu ini\n' > catatan.txt# fungsi validasi mengembalikan kode keluar
ujian_file() {
if [[ -f "$1" ]]; then
return 0
fi
return 1
}
ujian_file catatan.txt && printf 'catatan.txt ada\n'
ujian_file tidak-ada.txt || printf 'tidak-ada.txt tidak ditemukan\n'catatan.txt ada
tidak-ada.txt tidak ditemukanKode keluar inilah yang membuat fungsi dapat digunakan di dalam kondisi if, pasangan && dan ||, maupun di dalam perulangan, persis seperti perintah biasa. Untuk mengirim data keluar dari fungsi, fungsi mencetaknya ke keluaran standar dan pemanggil menangkapnya dengan substitusi perintah.
# hasil perhitungan dikembalikan lewat keluaran standar
jumlahkan() {
printf '%d\n' $(( $1 + $2 ))
}
hasil=$(jumlahkan 8500 2900)
printf 'total sementara: %s\n' "$hasil"total sementara: 11400Pemisahan peran ini penting untuk dijaga, yaitu return untuk keberhasilan dan keluaran standar untuk data. Kata exit tidak boleh digunakan di dalam fungsi untuk tujuan return, karena exit mengakhiri seluruh skrip atau shell yang berjalan, bukan hanya fungsi yang memanggilnya.
Variabel lokal#
Variabel yang ditugaskan di dalam fungsi tanpa kata local menjadi variabel global, sehingga menimpa nilai yang sudah ada di luar fungsi. Perilaku ini sulit dilacak pada skrip panjang karena perubahan terjadi jauh dari tempat asalnya. Demonstrasi berikut menunjukkan penimpaan yang terjadi tanpa local.
# tanpa local, variabel di dalam fungsi menimpa nilai di luar
nama="pencatat"
catat() {
nama="fungsi"
printf 'di dalam fungsi: %s\n' "$nama"
}
catat
printf 'di luar fungsi: %s\n' "$nama"di dalam fungsi: fungsi
di luar fungsi: fungsiNilai nama berubah menjadi fungsi di luar fungsi karena kedua tempat menunjuk variabel yang sama. Penambahan kata local di depan penugasan membuat variabel itu hidup hanya selama fungsi berjalan, dan nilai di luar fungsi tetap utuh.
# dengan local, perubahan terbatas pada fungsi
nama="pencatat"
catat_aman() {
local nama="fungsi"
printf 'di dalam fungsi: %s\n' "$nama"
}
catat_aman
printf 'di luar fungsi: %s\n' "$nama"di dalam fungsi: fungsi
di luar fungsi: pencatatAturan kerjanya sederhana, yaitu setiap variabel yang hanya dibutuhkan di dalam fungsi ditulis dengan local. Argumen seperti $1 sudah bersifat lokal secara bawaan, sehingga dua fungsi dapat menggunakan $1 pada pemanggilan yang berbeda tanpa saling mengganggu. Kebocoran variabel global terasa ringan pada skrip pendek, tetapi menjadi sumber error yang sulit dilacak pada skrip panjang, karena nilai yang hilang atau berubah harus ditelusuri ke seluruh fungsi yang pernah berjalan. Dengan local pada setiap variabel kerja, dampak fungsi terhadap skrip terbatas pada keluaran dan kode keluarnya, dan itulah yang membuat fungsi aman dipindahkan antarskrip.
Pemakaian ulang pada skrip#
Skrip berikut merangkum peran fungsi dalam merapikan logika, yaitu menghitung rata-rata nilai dan menentukan predikatnya. Perhitungan yang panjang tersembunyi di dalam fungsi, sedangkan bagian bawah skrip terbaca seperti urutan langkah yang jelas. Fungsi hitung_rata mengembalikan hasilnya melalui keluaran standar dan mengikuti kebiasaan variabel lokal.
#!/usr/bin/env bash
# rekap-nilai.sh: merangkum nilai ujian menjadi rata-rata dan predikat
set -euo pipefail
hitung_rata() {
# rata-rata dihitung dari seluruh argumen angka
local total=0
local banyak=0
local nilai
for nilai in "$@"; do
total=$((total + nilai))
banyak=$((banyak + 1))
done
printf '%d\n' "$((total / banyak))"
}
tentukan_predikat() {
if [[ $1 -ge 85 ]]; then
printf 'A'
elif [[ $1 -ge 70 ]]; then
printf 'B'
else
printf 'C'
fi
}
rata=$(hitung_rata 87 76 74)
predikat=$(tentukan_predikat "$rata")
printf 'Rata-rata: %d\n' "$rata"
printf 'Predikat : %s\n' "$predikat"# jalankan skrip rekap nilai
bash rekap-nilai.shRata-rata: 79
Predikat : BTiga nilai ujian diproses oleh hitung_rata yang menjumlahkannya lalu membaginya, dan hasil pembagiannya memperoleh predikat B dari tentukan_predikat. Perubahan aturan predikat cukup dilakukan di satu tempat, yaitu di dalam fungsinya, tanpa menyentuh bagian skrip yang memanggilnya. Inilah keuntungan mengemas logika ke dalam fungsi, yakni satu tempat yang jelas untuk setiap aturan.
Peran kedua fungsi dalam skrip ini sengaja dipisahkan, yaitu perhitungan dan klasifikasi, karena keduanya berubah dengan alasan yang berbeda. Aturan predikat dapat berubah mengikuti kebijakan penilaian, sedangkan cara menghitung rata-rata jarang berubah. Fungsi yang menangani satu hal dengan jelas juga mudah diuji secara terpisah, misalnya hitung_rata 100 0 yang seharusnya menghasilkan 50. Pola yang sama dapat diterapkan pada pekerjaan apa pun yang muncul di lebih dari satu tempat, sehingga fungsi menjadi unit pemakaian ulang yang paling kecil dalam skrip shell.
Kesalahan umum#
Memanggil fungsi sebelum definisinya dibaca. Bash mengeksekusi skrip dari atas ke bawah, sehingga pemanggilan yang berada di atas definisi berakhir dengan pesan command not found. Kesalahan ini mudah muncul ketika fungsi ditambahkan di akhir berkas padahal pemanggilannya sudah tertulis di atas. Susun definisi fungsi di bagian atas skrip, tepat setelah baris set -euo pipefail, sebelum baris pemanggilan pertama.
Mengira return mengirim data. return hanya menerima angka 0 sampai 255 sebagai kode keluar, sehingga return "$hasil" dengan angka besar gagal atau memberi makna yang salah. Data dikirim melalui keluaran standar dan ditangkap pemanggil dengan substitusi perintah, sebagaimana diperlihatkan oleh fungsi jumlahkan. Kata return juga hanya sah di dalam fungsi atau berkas yang dimuat melalui sourcing, dan penggunaannya di tempat lain memicu pesan error bash.
Variabel di dalam fungsi ditulis tanpa local. Tanpa local, penugasan di dalam fungsi menimpa variabel global dengan nama sama, dan efeknya muncul jauh setelah fungsi selesai. Tulis local pada setiap variabel kerja fungsi, termasuk variabel yang digunakan sebagai penghitung perulangan.
Ringkasan#
- Fungsi didefinisikan dengan
nama() { }, mendaftar pada shell saat definisi dibaca, dan dipanggil seperti perintah biasa. - Argumen masuk melalui
$1,$2, dan$#, dengan nomor yang mengikuti panggilan fungsi masing-masing. - Kode keluar melalui
returnmenyatakan keberhasilan, sedangkan data dikirim lewat keluaran standar dan ditangkap dengan substitusi perintah. - Kata
localmenjaga variabel kerja fungsi agar tidak menimpa nilai global, dan pengemasan logika ke dalam fungsi memberi satu tempat yang jelas untuk setiap aturan.
Referensi#
- GNU Bash Reference Manual — Free Software Foundation — dokumentasi resmi bash untuk definisi fungsi, variabel lokal, dan argumen posisi.
- Google Shell Style Guide — Google
— bagian Functions yang mengatur panjang fungsi, pemakaian
local, dan nilai baliknya.