Mengolah angka di dalam skrip: memahami keterbatasan integer bash, arithmetic expansion $(( )), operator + - * / % **, perintah let dan expr yang legacy, perhitungan floating point dengan bc, hingga membangun kalkulator pemakaian disk yang realistis.

Setelah di episode 7 sebelumnya kita membahas globbing & pathname expansion — bagaimana bash memperluas pola menjadi daftar file nyata di filesystem — pada episode kali ini kita berpindah dari teks dan file ke angka. Kemampuan berhitung mungkin tampak sepele, tapi hampir setiap skrip DevOps memerlukannya: menghitung persentase pemakaian disk, membandingkan waktu respons, menghitung jumlah retry, atau menentukan apakah ukuran file melebihi batas.
Masalahnya, bash bukanlah kalkulator. Ia adalah bahasa scripting yang dirancang di tahun 1989 untuk mengatur proses dan teks — dan aritmatikanya punya keterbatasan yang jarang disadari: hanya bisa mengolah bilangan bulat (integer). Kalian tidak bisa membagi 10 dengan 3 dan mendapatkan 3.33 dengan bash murni. Memahami keterbatasan ini — dan tahu alat yang tepat untuk menembusnya (bc) — adalah inti episode ini.
Coba bayangkan bash sebagai mesin kasir yang hanya mengenal uang bulat rupiah: ia bisa menjumlahkan 5000 + 2000, tetapi saat harus membagi 10000 menjadi 3, ia akan menjawab 3333 dan membuang sisanya — tanpa memberi tahu kalian bahwa ada sisa 1. Bagi beberapa tugas ini cukup; bagi tugas yang menuntut presisi (persentase, suku bunga, metrik), ini fatal.
Uji keterbatasan ini dengan cepat:
echo $(( 10 / 3 ))3Hasilnya 3, bukan 3.33. Tidak ada error, tidak ada peringatan — bash hanya menghitung pembagian integer, membuang sisanya. Ini adalah silent truncation: jawaban yang "hampir benar" tetapi salah presisi. Jika kalian menulis skrip yang menampilkan persentase pemakaian disk dengan cara ini, angka 99% bisa tampil padahal nilai aslinya 99.9% — atau, lebih parah, 0% untuk 4.9%. Untuk inilah kita perlu memahami kapan integer cukup, dan kapan harus membawa bc.
$(( )) — Bahasa Aritmatika BashCara paling umum dan direkomendasikan untuk berhitung di bash adalah arithmetic expansion $(( ekspresi )). Nilai ekspresi dihitung, dan hasilnya menggantikan seluruh ekspresi — persis seperti expansion lain ($(...), $var):
echo $(( 5 + 3 )) # penjumlahan
echo $(( 10 - 4 )) # pengurangan
echo $(( 6 * 7 )) # perkalian
echo $(( 20 / 5 )) # pembagian (integer!)
echo $(( 10 % 3 )) # sisa bagi (modulo)
echo $(( 2 ** 10 )) # pangkat8
6
42
4
1
1024Tabel operator lengkapnya:
| Operator | Arti | Contoh | Hasil |
|---|---|---|---|
+ | Penjumlahan | $(( 5 + 3 )) | 8 |
- | Pengurangan | $(( 10 - 4 )) | 6 |
* | Perkalian | $(( 6 * 7 )) | 42 |
/ | Pembagian integer (sisa dibuang) | $(( 20 / 6 )) | 3 |
% | Modulo (sisa bagi) | $(( 20 % 6 )) | 2 |
** | Pangkat | $(( 2 ** 10 )) | 1024 |
++ | Increment | (( i++ )) | tambah 1 |
-- | Decrement | (( i-- )) | kurang 1 |
+= | Assignment gabungan | (( total += 10 )) | total + 10 |
$(( )) — Tanpa $ Juga BolehIni salah satu keunikan bash: di dalam $(( )), nama variabel bisa ditulis tanpa tanda $. Bash otomatis membacanya sebagai angka:
#!/usr/bin/env bash
harga=15000
jumlah=4
total=$(( harga * jumlah ))
echo "Total bayar: Rp$total"Total bayar: Rp60000Perhatikan: harga dan jumlah dipakai tanpa $ di dalam $(( )). Ini berbeda dari expansion variabel biasa yang selalu butuh $. Keduanya sebenarnya boleh — $(( $harga * $jumlah )) juga valid — tapi menulis tanpa $ lebih bersih dan terhindar dari salah tafsir bash saat variabel berisi sintaks khusus. Ini mungkin tampak remeh, tapi memahami kenapa keduanya sama validnya akan menyelamatkan kalian dari teka-teki aneh nanti.
(( )) Tanpa $ — Sebagai PernyataanJika kalian menulis $(( )), hasilnya disisipkan ke dalam teks. Jika menulis (( )) tanpa $, ia menjadi pernyataan (statement) yang menjalankan perhitungan — dan yang lebih penting, hasil evaluasinya menentukan exit status:
0 (sukses).1 (gagal).#!/usr/bin/env bash
total=1500000
if (( total >= 1000000 )); then
echo "Total melewati satu juta — butuh persetujuan."
else
echo "Total di bawah ambang batas."
fiTotal melewati satu juta — butuh persetujuan.Pola (( ekspresi )) sebagai kondisi adalah salah satu idiom paling umum di skrip bash — ini akan kita manfaatkan penuh di episode 9 tentang pengkondisian. Untuk sekarang cukup pahami: (( )) berbicara lewat exit status, dan bash menerjemahkan "bukan nol = benar" ke dalam bahasa kondisi.
let dan expr: Pendahulu yang Masih Sering DitemuiSebelum $(( )) ada, bash menyediakan perintah let, dan sebelum itu, program eksternal expr. Kalian akan menemui keduanya di skrip lama, tutorial usang, atau di mesin dengan bash versi tua. Pahami cara membacanya agar tidak bingung — tapi jangan menulis kode baru dengan keduanya.
letlet mengevaluasi ekspresi aritmatika dan mengubah variabel (assignment), tanpa mencetak hasil:
let x=5+3
let "y = x * 2"
echo "x=$x, y=$y"x=8, y=16Perhatikan let "y = x * 2" butuh tanda kutip karena * akan dicocokkan dengan globbing oleh shell — momen yang bagus untuk mengingatkan: operator * di luar $(( )) atau let akan diperlakukan sebagai wildcard (episode 7), bukan perkalian.
expr — Program Eksternal yang Rapuhexpr adalah program eksternal (bukan bawaan shell) yang mengevaluasi ekspresi. Ia terkenal rapuh: operator harus di-spasi dan diberi escape, dan setiap variabel butuh $. Kesalahan sintaksnya sangat mudah terjadi:
x=5
y=3
echo "5 + 3 = $(expr $x + $y)"
echo "5 * 3 = $(expr $x \* $y)"5 + 3 = 8
5 * 3 = 15Lihat betapa rapuhnya: expr $x + $y harus dipisah spasi, dan * harus di-escape menjadi \* agar tidak menjadi wildcard. Setiap ekspresi juga melewati proses eksternal (lambat). Kesimpulan: expr adalah artefak sejarah — kenali untuk membacanya, tapi gunakan $(( )) untuk semua kode baru.
Tip
Aturan pilihan: tulis kode aritmatika baru dengan $(( )) (dan (( )) untuk kondisi). Pakai let hanya jika kalian memelihara skrip lama yang sudah memakainya, dan jangan pernah menulis expr baru — ia lambat, rapuh, dan memicu lebih banyak bug daripada yang diselesaikannya.
bc — Memecah Batas Integer dengan Bilangan PecahanBash hanya bisa integer. Ketika persentase, rata-rata, atau pembagian presisi diperlukan — katakanlah 10 / 3 = 3.33 — bash harus menyerah pada alat eksternal yang memang dirancang untuk itu: bc (basic calculator). bc bukan bagian dari bash; ia adalah program kalkulator berbasis teks yang telah ada di hampir semua sistem Unix/Linux.
Cara memakainya: kirim ekspresi sebagai input, terima hasilnya sebagai output:
echo "scale=2; 10 / 3" | bc3.33Variabel scale memberitahu bc berapa digit di belakang koma. Tanpa scale, bc berperilaku seperti bash: membagi integer. Bagian kiri dari pipe adalah perintah echo yang menyerahkan ekspresi; bagian kanan adalah bc yang menghitungnya. Ini memanfaatkan pipeline yang kita kenal sejak awal series: output satu program menjadi input program lain.
Agar hasil bc bisa dipakai di dalam logika skrip, bungkus dengan command substitution $(...):
#!/usr/bin/env bash
hasil=$(echo "scale=2; 10 / 3" | bc)
echo "Hasil: $hasil"Hasil: 3.33Perhatikan pola yang membedakan: $(( )) menghasilkan integer dari bash itu sendiri; $(echo ... | bc) menghasilkan string pecahan dari program eksternal. Keduanya tampak mirip secara visual tapi mekanismenya berbeda total. Saat kalian perlu bilangan bulat — $(( )). Saat kalian perlu pecahan/presisi — $(echo "scale=N; ekspresi" | bc).
Ada satu jebakan tersembunyi: bc menghasilkan string, bukan angka. Jadi [ "$hasil" -gt 5 ] tidak akan berfungsi untuk "3.33" — operator -gt hanya untuk integer. Untuk membandingkan hasil pecahan, biarkan bc yang membandingkannya:
#!/usr/bin/env bash
pemakaian=87.5
if [ "$(echo "$pemakaian > 80" | bc)" -eq 1 ]; then
echo "PERINGATAN: pemakaian melebihi 80%!"
fiPERINGATAN: pemakaian melebihi 80%!bc mengevaluasi 87.5 > 80 menjadi 1 (benar) atau 0 (salah) — dan hasilnya, yang berupa integer 1 atau 0, aman diperbandingkan dengan -eq. Ini adalah idiom standar untuk logika perbandingan pecahan di bash.
Saatnya merangkai semuanya menjadi skrip nyata: menghitung persentase pemakaian disk untuk sebuah direktori. Ini adalah tugas monitoring yang sangat umum — dan contoh sempurna di mana $(( )) dan bc bekerja sama.
#!/usr/bin/env bash
set -euo pipefail
target="${1:-$HOME}"
if [ ! -d "$target" ]; then
echo "Error: '$target' bukan direktori." >&2
exit 1
fi
total_bytes=$(du -s --block-size=1 "$target" | awk '{print $1}')
ruang_kapasitas=$(( total_bytes > 0 ? total_bytes : 1 ))
# Simulasi kapasitas partisi 10 GB (10 * 1024^3 byte)
kapasitas_partisi=$(( 10 * 1024 * 1024 * 1024 ))
persen=$(( ruang_kapasitas * 100 / kapasitas_partisi ))
echo "Direktori : $target"
echo "Ukuran : $(du -sh "$target" | cut -f1)"
echo "Kapasitas partisi: 10 GiB"
echo "Pemakaian : ${persen}%"./kalkulator_disk.sh /var/logDirektori : /var/log
Ukuran : 1.2G
Kapasitas partisi: 10 GiB
Pemakaian : 12%Mari bedah bagian-bagian pentingnya:
| Baris | Yang Terjadi | Mengapa |
|---|---|---|
du -s --block-size=1 | Ukur total byte direktori tanpa pembulatan | Kita butuh angka presisi sebelum dibagi |
${1:-$HOME} | Parameter expansion dengan default (episode parameter expansion) | Skrip tetap jalan tanpa argumen |
$(( ruang_kapasitas * 100 / kapasitas_partisi )) | Aritmatika integer untuk persen | Kalikan dulu dengan 100 agar persennya tidak selalu 0 |
total_bytes > 0 ? total_bytes : 1 | Operator ternary bash | Menghindari pembagian oleh nol (pitfall di bawah) |
Perhatikan urutan perkalian sebelum pembagian: $(( ruang_kapasitas * 100 / kapasitas_partisi )), bukan $(( ruang_kapasitas / kapasitas_partisi * 100 )). Mengapa? Karena pembagian integer membuang sisa. Jika urutannya dibalik, ruang_kapasitas / kapasitas_partisi akan menghasilkan 0 untuk sebagian besar direktori kecil — lalu 0 * 100 = 0%. Dengan mengalikan 100 dulu, presisi integer-nya lebih terjaga. Detail urutan operasi seperti inilah yang membedakan skrip yang benar dari skrip yang tampak benar.
Important
Hati-hati dengan urutan perkalian/pembagian integer. a / b * 100 dan a * 100 / b menghasilkan angka yang sangat berbeda karena truncation di pembagian. Kalau hasilnya mencurigakan, cek dulu: mana yang dibagi lebih dulu? Untuk persentase, selalu kalikan dulu, lalu bagi.
echo $(( 10 / 0 )) menghasilkan error bash "division by 0" dan, jika set -e aktif, skrip berhenti. Saat pembagi berasal dari data dinamis (ukuran file, input user), selalu validasi lebih dulu atau sediakan nilai aman — seperti operator ternary pada skrip di atas.
exprexpr 5 * 3 akan error "syntax error" karena * diperluas sebagai wildcard oleh shell sebelum expr melihatnya. Ini salah satu alasan expr dianggap rapuh — dan alasan lain untuk memakai $(( )) yang bebas dari masalah ini.
$(( )) tidak butuh $Kebalikan dari yang sering ditakuti: $(( $harga )) valid, tapi menulis $(( harga )) juga valid dan lebih bersih. Yang tidak valid adalah menulis $(( harga )) ketika harga belum di-set — nilainya dianggap 0. Tidak ada error, hanya jawaban 0. Selalu pastikan variabel sudah terisi.
$(( 5 / 2 )) = 2, bukan 2.5 — dan tidak ada peringatan. Ini penyebab utama bug "persen yang aneh" di skrip monitoring. Jika presisi dibutuhkan, segera beralih ke bc.
bc (string pecahan) dengan operator integer[ "$hasil" -gt 5 ] error saat $hasil berisi "3.33". Biarkan bc yang membandingkan, lalu uji hasil 1/0-nya dengan -eq.
** di luar $(( ))Operator pangkat ** hanya dikenali di dalam arithmetic context. Menulis echo 2 ** 10 di luar konteks aritmatika akan memicu globbing pada ** dan hasilnya bukan 1024. Selalu bungkus dengan $(( )).
Pada episode 8 ini, kita telah mempelajari cara bash mengolah angka: keterbatasan integer-only yang membuat bash bukan kalkulator penuh; arithmetic expansion $(( )) dengan operator + - * / % **; perbedaan (( )) sebagai pernyataan ber-exit-status; perintah legacy let dan expr yang perlu dikenali tapi tidak ditulis baru; bc sebagai jalan keluar untuk bilangan pecahan; serta pola praktik menghitung persentase pemakaian disk yang menggabungkan semuanya.
Inti yang harus dibawa pulang:
$(( )) untuk perhitungan integer; variabel di dalamnya boleh tanpa $.* sebelum / menjaga presisi persentase.bc dengan scale, hasilnya string — biarkan bc yang membandingkan.expr rapuh (quoting *), let usang — tulis kode baru dengan $(( )).Kemampuan berhitung ini akan jarang dipakai sendirian. Dalam skenario nyata, kalian hampir selalu berhitung sambil mengambil keputusan: jika pemakaian disk melebihi 80%, kirim peringatan; jika file backup tidak ada, buat; jika argumen kurang, tolak. Itu berarti kita butuh struktur pengambilan keputusan — dan di episode 9 selanjutnya, kita akan membahas Pengkondisian (Conditionals) & Test Operators — if/elif/else, perbedaan [ ] vs [[ ]], operator test untuk string, angka, dan file, hingga short-circuit &&/||. Sampai jumpa!