Perulangan mengulang satu blok perintah untuk setiap butir pada sebuah daftar, sehingga pekerjaan yang sama tidak perlu diketik berulang kali. Topik ini membahas perulangan for atas daftar dan atas berkas, perulangan while yang membaca baris demi baris, serta pola iterasi yang aman untuk nama berkas yang memuat spasi. Pekerjaan berulang atas banyak berkas dapat ditulis dalam satu blok yang singkat.
Perulangan for atas daftar#
Perulangan for mengambil setiap butir dari sebuah daftar, menyimpannya pada variabel, lalu menjalankan blok di antara do dan done satu kali untuk setiap butir. Bentuk paling sederhananya adalah daftar yang ditulis langsung pada baris for. Butir yang memuat spasi cukup ditulis di dalam tanda kutip agar tetap satu bagian.
# perulangan for atas daftar butir yang ditulis langsung
for butir in "susu formula" "telur" "beras"; do
printf 'membeli %s\n' "$butir"
donemembeli susu formula
membeli telur
membeli berasBlok di dalam perulangan dijalankan tiga kali, dan setiap kali variabel butir berisi satu butir. Blok itu boleh memuat sejumlah perintah apa pun, termasuk percabangan untuk memisahkan perlakuan atas setiap butir dan perulangan lain yang bersarang di dalamnya. Variabel perulangan mengikuti aturan variabel biasa, sehingga pemanggilannya tetap ditulis dengan tanda kutip ganda sesuai aturan pada topik variabel dan quoting.
Nama variabel perulangan sebaiknya menggambarkan isi butirnya, misalnya butir untuk daftar belanja dan berkas untuk daftar berkas, agar blok yang panjang tetap mudah dibaca. Daftar pada baris for tidak harus ditulis langsung, melainkan dapat berasal dari pengembangan glob, rentang angka, atau sumber lain yang menghasilkan beberapa kata. Bagian berikut membahas sumber daftar yang paling sering digunakan, yaitu daftar berkas.
Iterasi atas berkas dengan glob#
Daftar yang paling sering diproses adalah daftar berkas. Glob *.txt dikembangkan oleh bash menjadi daftar nama berkas yang cocok, dan perulangan for menerima daftar itu secara langsung. Keunggulan pentingnya adalah setiap nama berkas menjadi satu butir utuh, seberapa pun banyak spasi di dalamnya. Siapkan dahulu dua berkas contoh. Salah satunya memuat spasi pada namanya.
# siapkan berkas contoh, salah satunya memuat spasi pada namanya
printf 'isi catatan\n' > catatan.txt
printf 'isi laporan bulanan\n' > "laporan bulanan.txt"# glob mengembang menjadi satu kata utuh untuk setiap nama berkas
for berkas in *.txt; do
printf '%s\n' "$berkas"
donecatatan.txt
laporan bulanan.txtNama laporan bulanan.txt muncul utuh pada satu baris karena glob mengembang menjadi satu kata untuk setiap berkas. Pengembangan ini terjadi sebelum perulangan dimulai, sehingga isi blok tidak pernah melihat pola *.txt, melainkan hanya nama berkas yang sudah jadi. Tanda kutip ganda pada "$berkas" menjaga nama tetap utuh saat diteruskan ke perintah di dalam blok.
Urutan butir yang dihasilkan glob mengikuti urutan alfabetis, sehingga hasil perulangan tetap sama pada isi direktori yang sama. Pola *.txt hanya mencocokkan berkas pada direktori saat ini dan tidak menelusuri subdirektori, sebagaimana perilaku glob pada umumnya. Apabila perulangan perlu memproses berkas di banyak direktori, pola dengan jalur dapat ditulis langsung, misalnya arsip/*.txt, dan setiap nama yang dihasilkan memuat jalurnya secara lengkap.
Jebakan mengulang keluaran ls#
Pola lama yang masih sering ditemui adalah mengulang keluaran ls dengan substitusi perintah, seperti for berkas in $(ls *.txt). Pola ini terlihat bekerja pada berkas tanpa spasi, tetapi rusak begitu ada nama berkas yang memuat spasi. Jalankan perulangan berikut untuk melihat kegagalannya.
# pola yang rapuh: mengulang keluaran ls
for berkas in $(ls *.txt); do
printf '%s\n' "$berkas"
donecatatan.txt
laporan
bulanan.txtKeluaran ls diproses kembali oleh pecah kata, sehingga laporan bulanan.txt terpecah menjadi dua butir, yaitu laporan dan bulanan.txt. Tiga baris keluaran berasal dari dua berkas, dan blok di dalam perulangan menjalankan perintah pada nama yang tidak pernah ada. Pola glob *.txt langsung pada baris for tidak memiliki masalah ini karena pengembangannya terjadi sebelum pecah kata.
Perulangan while dan read#
Perulangan while menjalankan bloknya selama kondisinya berhasil, sehingga cocok untuk proses yang jumlah langkahnya tidak diketahui di awal. Pasangan yang paling sering digunakan adalah while read, yaitu membaca masukan baris demi baris selama barisnya masih ada. Siapkan berkas daftar tugas, lalu proses setiap barisnya dengan nomor urut.
# siapkan daftar tugas untuk perulangan while
printf 'menyusun laporan\nmengarsipkan berkas lama\nmenulis catatan rapat\n' > daftar-tugas.txt# while read membaca berkas baris demi baris
n=1
while IFS= read -r baris; do
printf '%d. %s\n' "$n" "$baris"
n=$((n + 1))
done < daftar-tugas.txt1. menyusun laporan
2. mengarsipkan berkas lama
3. menulis catatan rapatPenugasan IFS= di depan read menonaktifkan pemangkasan spasi di awal dan akhir baris, sedangkan flag -r menjaga tanda garis miring terbalik agar tidak diperlakukan sebagai karakter pengubah. Redirection done < daftar-tugas.txt menyuplai perulangan dengan isi berkas, dan read berhenti bersama perulangan saat akhir berkas tercapai karena read meninggalkan kode keluar gagal pada kondisi itu. Penghitung n bertambah setiap kali blok dilewati melalui ekspansi aritmetika $(( )).
Rentang angka pada for#
Bash menyediakan rentang angka bawaan dalam bentuk {awal..akhir}, yang dikembangkan menjadi daftar angka berurutan. Rentang dengan langkah ditulis {awal..akhir..langkah}. Bentuk ini menggantikan kebiasaan lama menggunakan $(seq 1 5) karena tidak melibatkan perintah tambahan.
# rentang angka bawaan bash, dengan dan tanpa langkah
for i in {1..5}; do
printf '%s ' "$i"
done
printf '\n'
for i in {1..10..2}; do
printf '%s ' "$i"
done
printf '\n'1 2 3 4 5
1 3 5 7 9Rentang pertama menghasilkan lima angka berurutan, sedangkan rentang kedua menghasilkan angka ganjil dari 1 sampai 9 karena langkahnya dua. Langkah dapat ditulis negatif untuk menghitung mundur, misalnya {5..1..-2} yang menghasilkan 5 3 1. Rentang angka biasanya digunakan untuk penghitung, nama berkas bernomor, atau percobaan yang jumlahnya ditentukan angka.
Rentang ini adalah pengembangan brace, yaitu fitur bash yang menyusun daftar sebelum perintah dijalankan, sehingga sifatnya mirip glob yang diperkenalkan di atas. Bentuk yang setara untuk shell lain adalah $(seq 1 5), yang tetap digunakan pada skrip lama dan pada shell yang tidak memiliki pengembangan rentang. Untuk skrip bash, rentang bawaan lebih disarankan karena tidak melahirkan proses tambahan dan tidak melalui pecah kata sama sekali.
Menerapkan perulangan pada skrip#
Skrip berikut merangkum pola yang aman, yaitu perulangan atas glob, uji -f di dalam blok, dan tanda kutip ganda pada setiap variabel. Uji -f menangani kasus direktori tanpa berkas .txt sama sekali, karena glob yang tidak menemukan kecocokan dibiarkan menjadi teks *.txt dan tentu bukan berkas biasa.
#!/usr/bin/env bash
# hitung-kata.sh: menghitung jumlah kata pada setiap berkas .txt
set -euo pipefail
total=0
for berkas in *.txt; do
if [[ -f "$berkas" ]]; then
kata=$(wc -w < "$berkas")
printf '%4d %s\n' "$kata" "$berkas"
total=$((total + kata))
fi
done
printf '%4d total\n' "$total"# jalankan skrip penghitung kata
bash hitung-kata.sh 2 catatan.txt
8 daftar-tugas.txt
3 laporan bulanan.txt
13 totalSetiap baris keluaran menampilkan jumlah kata dan nama berkasnya, termasuk laporan bulanan.txt yang namanya utuh berkat quoting. Penjumlahan pada total mengikuti urutan perulangan, sehingga baris terakhir menyajikan akumulasi seluruh berkas. Skrip ini ditulis untuk pekerjaan yang sebelumnya dikerjakan dengan perintah wc -w satu per satu, sesuai kebiasaan menyusun skrip dari pekerjaan interaktif.
Uji -f di dalam blok terlihat kecil, tetapi menjadi pelindung skrip saat direktori berubah keadaan. Pada direktori tanpa berkas .txt sama sekali, glob dibiarkan menjadi teks *.txt, uji -f menolaknya, dan skrip berakhir dengan total 0 tanpa pesan error. Tanpa uji itu, perintah di dalam blok akan menerima *.txt sebagai nama berkas dan melaporkan bahwa berkas tersebut tidak ada. Perluasan yang wajar untuk skrip ini antara lain menyaring berkas menurut pola lain atau menambahkan kolom baru pada laporannya, dan seluruhnya cukup disunting di dalam blok perulangan.
Kesalahan umum#
Mengulang keluaran ls atau hasil substitusi perintah lain. Pecah kata pada keluaran tersebut memecah nama berkas berisi spasi menjadi beberapa butir, sebagaimana diperlihatkan pada bagian jebakan. Ganti polanya dengan glob langsung pada baris for, atau dengan while read apabila daftar barisnya perlu diproses satu per satu.
Variabel perulangan ditulis tanpa quoting di dalam blok. Nama yang utuh pada saat perulangan dimulai bisa terpecah lagi di dalam blok apabila "$berkas" ditulis tanpa kutip ganda. Perulangan yang aman membutuhkan dua hal sekaligus, yaitu glob pada baris for dan kutip ganda pada setiap pemakaian variabelnya.
Penghitung dengan ((i++)) pada skrip strict mode. Ekspansi aritmetika ((i++)) meninggalkan kode keluar 1 apabila nilai i masih 0 sebelum naik, dan baris set -e menganggap itu kegagalan yang harus menghentikan skrip. Bentuk i=$((i + 1)) selalu menghasilkan nilai tanpa efek samping pada kode keluar, sehingga aman di dalam skrip yang menggunakan strict mode.
Ringkasan#
- Perulangan for menjalankan blok untuk setiap butir daftar, dan glob pada baris for menghasilkan satu butir utuh untuk setiap nama berkas.
- Mengulang keluaran ls melalui substitusi perintah memecah nama berkas berisi spasi dan harus diganti dengan glob langsung.
- Perulangan
while IFS= read -r barismemproses berkas baris demi baris, disuplai lewat redirection padadoneagar tetap berjalan di shell saat ini. - Rentang
{1..10}dan{1..10..2}menyediakan daftar angka untuk penghitung tanpa perintah tambahan.
Referensi#
- GNU Bash Reference Manual — Free Software Foundation — dokumentasi resmi bash untuk konstruksi perulangan for, while, dan brace expansion.
- The Linux Command Line — William Shotts — bab kendali alur yang membahas perulangan while dan for beserta pembacaan berkas baris demi baris.