D
P
0
← Semua artikel Read in English

Cache & CDN: Deploy Sukses, Perubahan Tak Muncul

Purge Cloudflare Cuma `purged 1 URLs`? zsh Tidak Memecah `$VAR` Tanpa Kutip Seperti bash

· · 8 menit baca
Purge Cloudflare Cuma `purged 1 URLs`? zsh Tidak Memecah `$VAR` Tanpa Kutip Seperti bash

Pada 1 Oktober 2026 saya mem-purge cache Cloudflare dengan sebuah daftar URL, dan keterangan yang muncul cuma purged 1 URLs. Daftar itu saya bangun sebagai satu string, lalu saya teruskan lewat variabel tanpa kutip. Yang sampai ke Cloudflare bukan daftar, melainkan satu "file" berisi seluruh string tadi.

Kata "file" itu bukan salah ketik. API purge Cloudflare menerima alamat yang mau dibuang lewat field files, satu URL per elemen, jadi satu elemen tetap dihitung satu, berapa pun alamat yang terselip di dalamnya. Sebab yang saya catat hari itu pendek: zsh tidak memecah kata pada $VAR tanpa kutip.

Kalimat itu bukan barang baru di catatan saya. Di satu proyek situs, sebab yang sama sudah tercatat lima kali, yang terakhir pada 23 September. Tiga hari kemudian dia muncul lagi di sapuan keamanan mesin saya sendiri, kali ini sebagai hasil nol yang palsu. Purge itu cuma salah satu bentuknya, dan juga bukan yang terakhir.

Di zsh, $VAR tanpa kutip tetap satu kata

Kebiasaannya datang dari bash. Di bash, variabel tanpa kutip dipecah menjadi beberapa kata menurut spasi, tab, dan baris baru, dan tiap potongnya menjadi argumen sendiri. Banyak skrip sengaja bersandar pada perilaku itu. zsh, shell bawaan macOS, tidak melakukannya. Variabel tanpa kutip di zsh tetap satu kata, apa pun isinya.

Contoh minimal berikut memakai nama netral dan bukan potongan skrip proyek mana pun:

v="satu dua tiga"
set -- $v
echo $#

Di bash, baris terakhir mencetak 3, dan $1 sampai $3 berisi tiga kata itu. Di zsh hasilnya 1. Di sana $1 memegang seluruh string, $2 dan $3 kosong, dan tidak ada error apa pun, karena dari sudut pandang zsh memang tidak ada yang salah. Pemecahan ala bash di zsh adalah opsi bernama SH_WORD_SPLIT yang mati secara bawaan, dan ada juga bentuk ${=v} untuk memaksa satu ekspansi dipecah. Keduanya membuat skrip bergantung pada zsh, dan bentuk kedua malah gagal dengan bad substitution begitu skripnya dijalankan bash.

Ini juga bukan kasus sintaks yang tidak dikenal shell lain, seperti here-string PowerShell yang dibaca CMD apa adanya. set -- $v sah di bash maupun zsh, dan keduanya menjalankannya tanpa keluhan. Yang berbeda cuma jumlah kata yang keluar.

Lima kali di proyek yang sama

Tempat yang paling sering kena adalah rig perekam yang saya pakai untuk mengaudit sebuah situs. Skrip utamanya, audit-rekam.sh, merekam delapan ukuran layar, membuat lembar kontak, dan mengambil frame 2 fps. Pada audit ulang 11 September 2026 malam, catatan saya menulis bahwa set -- $v di zsh tidak memecah kata dan semua layar terekam di 1440x900. Delapan ukuran yang seharusnya berbeda keluar sama semua.

Angka 1440x900 yang sama muncul di kode perekamnya sendiri. rekam.cjs memakai lebar 1440 dan tinggi 900 sebagai nilai cadangan bila --w dan --h tidak ada, nol, atau bukan angka:

const w = +args.w || 1440
const h = +args.h || 900

Tanda + mengubah argumen menjadi angka. Argumen yang tidak ada atau berisi teks yang bukan angka menjadi NaN, string kosong menjadi 0, dan || memperlakukan keduanya sebagai salah, jadi apa pun yang bukan angka berubah diam-diam menjadi 1440x900. Catatan saya tidak menyimpan bentuk loop yang dipakai waktu itu, jadi argumen persisnya tidak bisa saya tunjukkan. Yang bisa saya tunjukkan adalah dua hal yang saling cocok. zsh menaruh seluruh string di satu argumen dan membiarkan sisanya kosong, sementara perekamnya mengubah lebar dan tinggi yang tidak sampai sebagai angka menjadi persis ukuran yang muncul di semua rekaman. Tidak ada lapisan yang protes, dan komentar di kepala skripnya sekarang mencatat akibatnya:

# Bash, bukan zsh: zsh tidak memecah kata pada `set -- $v`, dan itu sudah
# tiga kali membuat label dan ukuran salah tanpa satu pun galat.

Aturan yang saya tulis bersama kejadian 1440x900 berbunyi wajib bash, bukan zsh. Pada hari yang sama, di tengah verifikasi sesudah tambalan situsnya, set -- $v di zsh tercatat memakan korban keempat, dan aturannya saya pertegas: loop ukuran layar hanya boleh dijalankan lewat skrip bash di berkas.

Korban kelima datang 23 September dari arah yang tidak disebut aturan itu. Untuk audit hari itu saya membuat dua klon APFS situsnya, lalu keduanya dibuild dan dilayani astro preview di port 4411 dan 4412. Perintah yang menyalakan dua server pratinjau itu memakai set -- $pair. Itu bukan loop ukuran layar, jadi aturan 11 September tidak menjangkaunya, padahal sebabnya sama persis.

Yang sekarang tertulis di skripnya

Perbaikan rig itu ada di kode, bukan cuma di catatan. audit-rekam.sh diawali shebang #!/usr/bin/env bash, dan cara pakai di komentarnya memanggil bash secara eksplisit. Delapan ukuran layarnya juga tidak lagi diulang dari variabel. Masing-masing ditulis sebagai satu baris panggilan dengan argumen eksplisit:

rekam desktop-besar 1920 1080 1 wheel 800
rekam desktop 1440 900 1 wheel 700
rekam laptop-pendek 1280 720 1 wheel 700
rekam tablet-lebar 1024 768 2 touch 900
rekam tablet 768 1024 2 touch 900
rekam ponsel-besar 430 932 2 touch 900
rekam ponsel 390 844 2 touch 900
rekam ponsel-kecil 360 780 2 touch 900

Fungsi rekam mengambil label, lebar, tinggi, dpr, mode, dan kecepatan dari enam argumen posisi. Di baris seperti ini, argumen sudah dipisah oleh tata bahasa perintah sebelum ada ekspansi apa pun, jadi bash dan zsh membacanya dengan cara yang sama. Delapan baris memang lebih panjang daripada satu loop, tapi tidak satu pun yang bergantung pada pemecahan kata.

Statusnya saya tulis apa adanya. Bentuk ini sudah terpasang di skrip, tapi catatan saya tidak memuat uji khusus untuk perbaikan shell-nya sendiri. Verifikasi 11 September menguji tambalan situsnya, bukan rig-nya, dan korban keempat justru tercatat di tengah verifikasi itu.

Nol palsu di sapuan keamanan, dan profil AWS yang ditolak

Pada 26 September 2026 saya menjalankan sapuan ulang keamanan atas git hook dan npm install di mesin ini, hanya dengan find dan grep. Cakupannya 47 repo di folder home, dengan 43 folder hooks berisi 602 berkas yang semuanya .sample. Di tengah sapuan itu, find $R dengan R berisi banyak path tidak dipecah oleh zsh, dan hasilnya nol palsu. find menerima seluruh isi R sebagai satu path yang tidak ada, jadi panggilan itu tidak memeriksa satu berkas pun.

Ini bentuk paling berbahaya dari jebakan yang sama. Di rig perekam, hasil yang salah masih bisa ketahuan dari rekamannya. Di sapuan keamanan, nol adalah jawaban yang diharapkan, dan nol palsu terbaca persis seperti mesin yang bersih. Catatan hari itu menyebutnya jebakan yang terulang lagi, dengan aturan bahwa sapuan multi-path wajib berupa skrip bash dengan array. Catatan yang sama memuat angka hasil sapuan, tapi tidak menjelaskan langkah apa yang diambil sesudah nol palsu itu ketahuan.

Catatan akses AWS saya memuat bentuk lain lagi, dan yang ini datang sesudah purge, pada 6 Oktober. Di zsh, $P tanpa kutip yang berisi --profile x --region y terbaca sebagai satu kata, dan CLI-nya menolak. Cara yang saya catat untuk zsh adalah meneruskan profil lewat variabel environment di depan perintah aws. Nilai di bawah ini sudah diganti dengan nama netral:

AWS_PROFILE=nama-profil AWS_REGION=nama-region AWS_PAGER="" aws ...

Penugasan di depan perintah dipisah oleh tata bahasa shell, bukan oleh ekspansi variabel, jadi tidak ada yang perlu dipecah, dan bentuk ini berjalan sama di bash.

Obatnya: berhenti bergantung pada pemecahan kata

Untuk purge 1 Oktober, obat yang saya catat adalah membangun array lalu meneruskannya sebagai "${arr[@]}". Dalam contoh minimal dengan nama netral, bedanya kelihatan dari jumlah argumen:

urls=(https://contoh.test/a https://contoh.test/b https://contoh.test/c)
set -- "${urls[@]}"
echo $#

Kali ini bash dan zsh sama-sama mencetak 3. Bentuk "${arr[@]}" mengembang menjadi satu kata per elemen di kedua shell, dan elemen yang kebetulan berisi spasi tetap utuh. Tidak ada opsi yang perlu dinyalakan dan tidak ada kebiasaan shell yang perlu diingat. Obat itu juga langsung saya pakai pada purge yang asli. Beberapa detik sesudah keterangan purged 1 URLs keluar, daftar yang sama saya kirim ulang sebagai array berisi 16 elemen, dan keterangannya berubah menjadi purged 16 URLs, 0 failed. Dua URL yang saya periksa sesudahnya menjawab dengan cf-cache-status: MISS, artinya keduanya diambil langsung dari server asal, bukan dari cache.

Untuk loop dan sapuan yang lebih panjang, aturan-aturan tadi mengarah ke tempat yang sama, yaitu skrip bash di berkas dengan shebang yang menyebut bash. Skrip seperti itu dijalankan bash walaupun terminalnya zsh, asal dipanggil sebagai ./skrip.sh atau bash skrip.sh. Menyalin isinya baris per baris ke prompt zsh, memanggilnya lewat source, atau menjalankannya dengan zsh skrip.sh membuang jaminan itu.

Aturan yang tertinggal satu bentuk

Kalau dijajarkan, tiap aturan yang saya tulis sampai akhir September ternyata menamai bentuk yang baru saja menggigit. Rekaman 1440x900 melahirkan aturan untuk rig perekam, korban keempat mempertegasnya menjadi aturan untuk loop ukuran layar, dan nol palsu di sapuan keamanan melahirkan aturan untuk sapuan multi-path. Kejadian berikutnya selalu datang dari arah yang belum disebut aturan mana pun: dua server pratinjau pada 23 September, find pada 26 September, lalu daftar URL untuk purge pada 1 Oktober.

Catatan dari purge itu sebenarnya sudah lebih lebar daripada aturan-aturan sebelumnya. Sebabnya ditulis untuk $VAR tanpa kutip apa pun, bukan untuk purge saja, lengkap dengan obat array. Pada 6 Oktober, $P berisi opsi profil AWS tetap terkirim tanpa kutip, dan obat yang saya catat sesudahnya kembali khusus untuk bentuk itu, yaitu profil lewat environment.

Jadi yang saya tarik dari deretan itu bukan aturan baru untuk bentuk berikutnya. Batas yang lebih lebar pun tidak saya anggap jaminan, karena bentuk umumnya sudah tertulis lima hari sebelum $P lolos. Isinya tetap saya pegang: begitu sebuah perintah butuh beberapa argumen dari satu variabel, jangan titipkan pemecahannya ke shell. Masukkan nilainya ke array dan teruskan sebagai "${arr[@]}", atau pindahkan perintahnya ke skrip bash di berkas. Keduanya bekerja sama di bash maupun zsh, tapi keduanya baru menolong kalau dipilih saat perintahnya ditulis, dan langkah itulah yang terlewat pada 6 Oktober.

Keterangan purged 1 URLs itu sebenarnya jujur sejak awal. Shell mengirim satu kata, Cloudflare menerima satu entri, dan angka satu itu persis menggambarkan yang terjadi. Yang keliru cuma anggapan saya tentang berapa kata yang dibawa sebuah variabel.