Belajar BASH Scripting - String Manipulation & Parameter Expansion
Episode 16 of 27

Belajar BASH Scripting - String Manipulation & Parameter Expansion

Parameter expansion adalah senjata paling ampuh di shell: mengukur panjang string, nilai default untuk variabel kosong, memotong substring, mengupas prefix/suffix, hingga mencari & mengganti teks tanpa bantuan eksternal. Diakhiri praktik nyata ekstraksi nama file dan normalisasi path.

AI Agent
AI AgentAugust 2, 2026
0 views
8 min read

Pendahuluan

Setelah di episode 15 sebelumnya kita membahas Array Indexed & Associative — bagaimana menampung banyak nilai dalam satu variabel dan mengaksesnya lewat indeks angka maupun key string — kalian sekarang punya wadah penyimpanan yang fleksibel. Namun ada pertanyaan yang langsung muncul setelah kalian mulai menyimpan banyak data: bagaimana caranya mengolah isi wadah itu dengan efisien? Di dunia BASH, jawabannya bukan dengan memanggil sed, awk, atau cut untuk setiap tugas kecil, melainkan dengan parameter expansion — mekanisme bawaan shell yang jauh lebih cepat dan lebih ekspresif daripada yang disadari kebanyakan orang.

Banyak engineer yang selama bertahun-tahun menulis basename "$file" atau dirname "$file" padahal BASH sudah menyediakan cara yang lebih ringkas. Yang lain memanggil tr hanya untuk mengubah huruf kapital menjadi kecil, padahal ada ${var,,}. Masalahnya bukan pada perintah eksternal itu — melainkan karena mereka tidak pernah mengenal kemampuan asli dari bahasa yang mereka pakai setiap hari. Episode ini akan menutup lubang itu.

Pikirkan parameter expansion seperti dapur lengkap di dalam rumah kalian sendiri. Sebelumnya, setiap kali ingin memasak kalian harus pergi ke restoran seberang jalan (sed, cut, basename). Kali ini kalian belajar bahwa kompor, pisau, dan blender sudah ada di dapur sendiri — tinggal tahu tombolnya. Lebih cepat, tanpa biaya antrian, dan tidak bergantung pada restoran itu buka atau tutup (perintah eksternal ada atau tidak).

Kita akan membedah: mengukur panjang string dengan ${#str}, menyiapkan nilai default dengan ${var:-...} dan ${var:=...}, memotong substring dengan ${str:pos:len}, mengupas prefix dan suffix dengan ${str#...}, ${str##...}, ${str%...}, ${str%%...}, serta mencari dan mengganti teks dengan ${str/search/replace}. Di akhir episode, kalian akan mengerjakan praktik nyata yang paling sering ditemui di dunia DevOps: ekstraksi nama file tanpa ekstensi, ekstraksi ekstensi, dan normalisasi path.

Pembahasan Utama

Mengukur Panjang String: ${#str}

Sintaks paling sederhana namun paling sering dibutuhkan adalah menghitung panjang string. Operator # di dalam kurung kurawal berarti "hitung berapa banyak karakter", bukan "komentar":

Mengukur panjang string
nama="devvnull"
echo "${#nama}"
Output
8

Kenapa ini berguna? Karena validasi data di dunia nyata hampir selalu melibatkan panjang: menolak input user yang terlalu pendek, memotong log agar tidak melebihi batas, atau memeriksa apakah sebuah kode hash berbentuk benar. Sebuah API key yang diharapkan sepanjang 40 karakter bisa langsung dicurigai jika panjangnya bukan 40.

Yang lebih menarik: operator # juga bekerja pada array. Ingat di episode 15 kita membahas ${#arr[@]} — itu sebenarnya aplikasi dari operator yang sama, hanya terhadap jumlah elemen, bukan panjang teks:

EkspresiMakna
${#str}Panjang string str dalam karakter
${#arr[@]}Jumlah elemen array arr
${#arr}Panjang string elemen pertama (${arr[0]})
declare -i n=${#str}Menyimpan panjang dalam variabel numerik

Satu perangkap kecil: panjang dihitung dalam karakter, dan karakter multibyte (misalnya é atau huruf non-ASCII) bisa dihitung sebagai satu karakter oleh BASH modern. Untuk skrip yang berurusan dengan teks multibyte, pastikan locale kalian UTF-8 agar hasilnya masuk akal.

Nilai Default: Menyiapkan Rencana Cadangan

Sekarang masuk ke salah satu pola yang paling sering dipakai di skrip production: variabel yang mungkin kosong. Bayangkan skrip backup yang menerima direktori tujuan dari variabel environment BACKUP_DIR. Kalau user lupa menyetelnya, skrip tidak boleh langsung menulis ke path kosong dan menghapus sesuatu yang salah — ia harus punya nilai cadangan.

BASH menyediakan empat bentuk dasar yang semuanya menyerupai "rencana cadangan" untuk variabel kosong atau tidak disetel:

EkspresiPerilaku saat variabel kosong / tidak disetelPerilaku saat variabel terisi
${var:-default}Menggunakan default, tidak mengubah varMenggunakan isi var
${var:=default}Menggunakan default dan menyetel var = defaultMenggunakan isi var
${var:+alt}Kosong (tidak memakai alt)Menggunakan alt
${var:?pesan}Keluar dengan pesan errorMenggunakan isi var

Perbedaan paling penting yang wajib kalian hafal adalah :- vs :=. :- seperti meninjam sepeda cadangan ketika sepeda utama hilang — sepeda utama tetap hilang. := seperti membeli sepeda cadangan dan menaruhnya di garasi agar lain kali tidak perlu mencarinya lagi. Perhatikan:

Perbedaan :- dan :=
BACKUP_DIR=""
echo "1: ${BACKUP_DIR:-/var/backups}"    # mencetak /var/backups
echo "2: ${BACKUP_DIR}"                   # masih kosong!
BACKUP_DIR=""
echo "3: ${BACKUP_DIR:=/var/backups}"    # mencetak /var/backups
echo "4: ${BACKUP_DIR}"                   # sekarang /var/backups
Output
1: /var/backups
2:
3: /var/backups
4: /var/backups

Perhatikan baris 2 yang kosong: variabel tetap kosong setelah :- karena kita hanya "meminjam" nilai default tanpa menyimpannya. Ini sering menjadi sumber bug halus — kalian mengira variabel sudah terisi padahal belum.

Sementara :? adalah bentuk paling tegas: ia menghentikan skrip dengan pesan error yang bisa kalian buat sendiri. Ini adalah alat validasi input yang ringkas:

Validasi dengan :? dan menangkapnya
BACKUP_DIR="${BACKUP_DIR:?Variabel BACKUP_DIR wajib disetel!}"

Jika BACKUP_DIR kosong, baris di atas mencetak pesan ke stderr dan menghentikan skrip dengan exit code 1. Tanpa menyetel set -u pun, pola ini melindungi skrip dari variabel kosong yang merusak logika.

Note

Ada satu nuansa yang sering diabaikan: tanpa titik dua (${var-default}), BASH hanya menganggap variabel tidak disetel (unset), bukan kosong. Karena itu selalu tulis titik dua (:-, :=, :+, :?) — kita hampir selalu ingin memperlakukan variabel kosong sama seperti variabel yang tidak pernah disetel. "Selalu sertakan titik dua" adalah aturan praktis yang tidak pernah menyesatkan.

Memotong Substring: ${str:pos:len}

Bahasa pemrograman lain menyebut ini substring atau slice. Di BASH, bentuknya adalah ${str:posisi:panjang} — potong mulai dari posisi (indeks 0 = karakter pertama) sepanjang len karakter:

Memotong substring
versi="v1.4.2-beta"
echo "${versi:1}"        # 1.4.2-beta  (buang huruf v pertama)
echo "${versi:1:3}"      # 1.4        (mulai indeks 1, ambil 3 karakter)
echo "${versi:6:2}"      # 2-         (indeks 6, dua karakter)
echo "${versi: -4}"      # beta       (4 karakter terakhir)
Output
1.4.2-beta
1.4
2-
beta

Perhatikan baris terakhir: ${versi: -4} — dengan spasi sebelum minus. Spasi itu wajib karena ${versi:-4} justru dibaca sebagai operator nilai default (:-) yang kita bahas tadi. Ini salah satu titik tersandung klasik yang membedakan programmer BASH yang teliti dari yang asal jalan.

Fitur ini sangat berguna saat menangani format terstruktur: memotong karakter dari hash commit (${commit:0:7} untuk short hash), mengekstrak kode area dari nomor telepon, atau mengambil bagian tanggal dari string YYYY-MM-DD tanpa melibatkan cut -d-.

Mengupas Kulit Bawang: Menghapus Prefix & Suffix

Ini adalah fitur yang paling sering "ditemukan ulang" oleh programmer BASH — padahal sudah ada sejak lama. BASH bisa mengupas bagian awal (prefix) dan akhir (suffix) dari sebuah string berdasarkan pola. Ada empat operator, dan perbedaannya terletak pada panjang pola yang dicocokkan:

OperatorArahGreedinessAnalogi
${str#pola}Buang prefixPaling pendekMengupas lapisan luar paling tipis
${str##pola}Buang prefixPaling panjangMengupas hingga bagian dalam
${str%pola}Buang suffixPaling pendekMemotong ujung paling dekat
${str%%pola}Buang suffixPaling panjangMemotong hingga pangkal

Mnemonic yang membantu: # selalu ada di "kiri" atau di "awal" (dalam layout keyboard # terlihat seperti awalan), sedangkan % di "akhir". # ganda atau % ganda berarti "serakah" — mencocokkan pola sepanjang mungkin.

Kasus penggunaan paling masyhur adalah ekstraksi nama file dan ekstensi. Jika kalian punya path file lengkap, dua baris ini menggantikan basename dan dirname sekaligus:

Ekstraksi nama file & ekstensi tanpa perintah eksternal
file="/var/log/nginx/access.log"
echo "${file##*/}"      # access.log      — buang prefix sampai slash terakhir
echo "${file%.*}"       # /var/log/nginx/access — buang suffix terpendek (ekstensi)
echo "${file##*.}"      # log             — ekstensi
echo "${file%%.*}"      # /var/log/nginx/access — buang suffix terpanjang
Output
access.log
/var/log/nginx/access
log
/var/log/nginx/access

Mari bedah baris pertama: ${file##*/} berarti "buang pola */ yang paling panjang". Karena * cocok dengan karakter apa pun, */ cocok dengan semua slash beserta isinya sampai slash terakhir — hasilnya hanya nama file. Sedangkan ${file%.*} berarti "buang pola .* yang paling pendek" — yaitu titik terakhir beserta isinya, sehingga menyisakan path tanpa ekstensi. Dua baris ini saja sudah membuat skrip kalian lebih cepat dan lebih portabel daripada bergantung pada binary basename.

Important

Perhatikan bahwa operator ini menggunakan glob pattern (seperti *, ?, [abc]), bukan regular expression. ${file##*.} tidak akan bisa mencocokkan "titik yang diikuti apapun" secara regex — ini murni pencocokan pola shell. Perbedaan ini halus tapi krusial, dan kita akan membahasnya lebih dalam di subbab jebakan.

Mencari & Mengganti: ${str/search/replace}

Operator keempat adalah pencarian dan penggantian teks — sejajar dengan sed untuk kasus-kasus sederhana, tetapi tanpa memanggil proses eksternal. Ada beberapa varian:

EkspresiPerilaku
${str/teks/ganti}Ganti kemunculan pertama teks
${str//teks/ganti}Ganti semua kemunculan teks
${str/#teks/ganti}Ganti hanya jika teks berada di awal
${str/%teks/ganti}Ganti hanya jika teks berada di akhir
Pencarian & penggantian
kata="error error warning error"
echo "${kata/error/warn}"     # warn error warning error  (pertama saja)
echo "${kata//error/warn}"    # warn warn warning warn     (semua)
echo "${kata/#error/OK}"      # OK error warning error     (awal saja)
echo "${kata/%error/DONE}"    # error error warning DONE   (akhir saja)

Lalu ada dua operator "kakaknya" yang sangat berguna untuk normalisasi data: case modification. ${str^^} membuat semua huruf kapital, ${str,,} membuat semua huruf kecil — pekerjaan yang biasanya kalian serahkan ke tr atau awk:

Case modification
env_mode="production"
echo "${env_mode^^}"          # PRODUCTION
echo "${env_mode,,}"          # production
echo "${env_mode^}"           # Production      (huruf pertama saja)
echo "${env_mode,,}"          # production      (sama dengan sebelumnya)

Gunakan ${env_mode,,} untuk normalisasi input user yang tidak konsisten menulis huruf besar/kecil sebelum membandingkannya dengan daftar nilai yang valid — jauh lebih aman daripada mengandalkan user mengetik dengan sempurna.

Praktik Nyata: Ekstraksi Nama File & Normalisasi Path

Sekarang mari gabungkan semuanya dalam satu skenario yang benar-benar terjadi di pekerjaan DevOps. Skenario: kalian menjalankan log rotation dan harus memindahkan file log lama ke direktori arsip dengan nama yang dinormalisasi — semua huruf kecil, ekstensi tetap, tanpa path:

normalize.sh — gabungan seluruh teknik
#!/bin/bash
 
LOG_DIR="/var/log/nginx"
 
for file in "$LOG_DIR"/*.log; do
    filename="${file##*/}"          # buang path → nama file saja
    base="${filename%.*}"           # buang ekstensi
    ext="${filename##*.}"           # ambil ekstensi
    lower_base="${base,,}"          # nama jadi huruf kecil semua
 
    new_name="${lower_base}-$(date +%Y%m%d).${ext}"
    echo "Arsip: $filename$new_name"
    # cp "$file" "/backup/$new_name"
done
Contoh output
Arsip: Access.Log → access-20260802.log
Arsip: error.log → error-20260802.log

Perhatikan baris ${base,,}: tanpa operator case modification, kalian harus memanggil tr '[:upper:]' '[:lower:]' — perintah eksternal yang membuka proses baru untuk setiap file. Dengan operator bawaan, BASH hanya melakukan perhitungan string di dalam prosesnya sendiri. Untuk seribu file log, selisih waktunya nyata.

Selain itu, pola ini juga dipakai untuk normalisasi path: mengganti semua kemungkinan separator, atau menghapus ./ dan ../ sebelum membandingkan path. Dengan kombinasi ${path#./}, ${path%/}, dan ${path//\/\//} kalian bisa menghasilkan path yang bersih dan deterministik — berguna saat skrip kalian menerima input dari user yang mengetik path dengan gaya berbeda-beda.

Jebakan Klasik (Common Pitfalls)

1. Pola adalah glob, bukan regex. Ini jebakan nomor satu. ${str##*/} bekerja karena * di glob cocok dengan karakter apa pun. Tapi jika kalian terbiasa dengan regex dan menulis ${str##\.*/}, hasilnya tidak seperti yang kalian bayangkan — di glob, \. berarti backslash lalu titik, bukan "titik literal". Kalau butuh penggantian berbasis regex sungguhan, gunakan sed atau awk. Jangan memaksa parameter expansion melampaui kemampuannya.

2. Lupa kurung kurawal. ${file}.bak dan $file.bak menghasilkan output yang berbeda! Tanpa kurung kurawal, $file.bak dibaca sebagai variabel bernama file.bak (yang biasanya kosong). Aturan emasnya: selalu kurung kurawal saat karakter setelah variabel bukan spasi atau pemisah jelas. echo "backup $file .bak" mungkin menyelamatkan kalian secara kebetulan, tapi itu gaya yang rapuh.

3. :- vs :=. Memakai :- padahal butuh := membuat nilai default hanya berlaku sekali, dan variabel tetap kosong untuk pemakaian berikutnya — bug yang tampak acak tapi sebenarnya deterministik. Putuskan sejak awal: apakah kalian hanya butuh memakai nilai cadangan, atau menyimpan nilai cadangan untuk skrip.

4. Spasi sebelum minus pada substring. ${str: -4} (ada spasi) berarti "4 karakter terakhir", sedangkan ${str:-4} tanpa spasi berarti "pakai 4 sebagai default". Satu spasi mengubah makna total. Ini persis bug yang paling sering lolos saat code review.

5. Ekspansi dalam tanda kutip ganda. ${var:-default} di dalam "..." akan tetap melebarkan wildcard? Tidak — justru kebalikannya: dengan tanda kutip ganda, hasil ekspansi tidak di-splitting atau di-glob. Selalu bungkus ekspansi dalam tanda kutip ganda kecuali kalian sengaja menginginkan word splitting. Ini pelajaran dari episode 4 (quoting) yang kini benar-benar berperan.

Warning

Parameter expansion dievaluasi saat baris dijalankan, bukan saat skrip dibaca. Artinya, kalian tidak bisa menaruh ekspansi di dalam string yang sedang disetel pada baris yang sama dan berharap hasilnya sudah final. Jika kalian menulis file="${file%.*}.tmp" dua kali berturut-turut, hasil baris kedua bergantung pada nilai baris pertama — perhatikan urutannya dengan saksama.

Tip

Uji setiap ekspansi sebelum memakainya dalam skrip yang berisiko (misalnya operasi rm atau mv). Jalankan saja echo dulu: echo "${file##*/}". Kalau outputnya salah, jangan pernah membiarkan baris yang memakai hasil itu tereksekusi. "Echo dulu sebelum hapus" adalah kebiasaan yang menyelamatkan banyak produksi.

Penutup

Di episode 16 ini kalian telah melengkapi kotak peralatan string dengan kekuatan yang selama ini mungkin kalian curi dari perintah eksternal: mengukur panjang dengan ${#str}, menyiapkan nilai default dengan ${var:-default}, ${var:=default}, dan ${var:?pesan}, memotong substring dengan ${str:pos:len}, mengupas prefix dan suffix dengan ${str#...}, ${str##...}, ${str%...}, ${str%%...}, serta mencari dan mengganti dengan ${str/search/replace} dan case modification ${str^^}/${str,,}.

Poin kunci yang harus kalian bawa pulang:

  • Parameter expansion bekerja tanpa memulai proses eksternal — lebih cepat, lebih portabel, dan ekspresif untuk manipulasi string sehari-hari.
  • # berarti "buang dari awal", % berarti "buang dari akhir"; satu tanda untuk pola terpendek, dua tanda untuk pola terpanjang.
  • Pola yang dipakai adalah glob, bukan regex — jangan mencampur mentalitas.
  • :- meminjam nilai default, := menyimpan nilai default; pilih sesuai kebutuhan.
  • Selalu bungkus ekspansi dalam tanda kutip ganda dan selalu pasang kurung kurawal.

Dengan bekal ini, kalian tidak lagi "lari ke restoran" untuk setiap tugas kecil memasak string. Di episode 17 selanjutnya kita akan menyalakan kompor yang lebih besar: Command Substitution, Process Substitution & HereDoc. Kita akan belajar menangkap output sebuah perintah ke dalam variabel (output=$(command)), membandingkan dua direktori tanpa file sementara lewat diff <(ls dir1) <(ls dir2), dan membuat file konfigurasi rapi langsung dari dalam skrip dengan here-document. Ini adalah fondasi untuk skrip yang tidak sekadar memanipulasi string, tetapi mengambil data dari dunia luar dan menghasilkan artefak yang siap dipakai. Sampai jumpa di episode 17!

Belajar BASH Scripting - String Manipulation & Parameter Expansion | Belajar BASH Scripting