Bagaimana Saya Mengganti Nama Aplikasi di Seluruh Isi Folder Project
On this page
- Pendahuluan
- A. Nama yang memang tak pernah tinggal di satu tempat
- B. Untuk apa ia dipakai, dan untuk apa tidak
- C. Enam hambatan yang menghalangi
- Metode
- A. Satu baris, dan empat keputusan di dalamnya
- B. Kenapa filesystem, bukan editor
- C. Selisih antara + dan \;
- D. Menyunting di tempat, dan di mana jaring pengamannya sebenarnya
- E. Urutan argumen, dipinjam bukan direka
- F. Yang ia janjikan tidak disentuh
- Hasil dan Pembahasan
- A. Apa yang dibeli tiga baris
- B. Bagian yang tidak ada di dalam script
- C. Ketika penggantian salah bentuk
- Simpulan
- Istilah
Pendahuluan
A. Nama yang memang tak pernah tinggal di satu tempat
Project baru jarang lahir dari folder kosong. Ia lahir dari folder yang sudah jalan — sebuah template, atau project terakhir yang bentuk persoalannya mirip. Menyalinnya cepat. Yang lama justru sesudahnya: salinan itu masih merasa dirinya project yang lama.
Nama lamanya ada di config. Ada di namespace separuh class. Ada di prefix tabel, di nama queue, di channel log, di komentar yang ditulis orang dua tahun lalu. Ada di domain yang tercetak ke URL hasil generate. Tak satu pun keliru dengan cara yang menarik. Semuanya cuma identitas lama, masih dinyatakan, di puluhan tempat sekaligus.
Tantangannya: mengganti identitas aplikasi — nama, prefix, domain — di seluruh isi folder project, tanpa membuka file-nya satu per satu.
Kalimat itu terdengar seperti pekerjaan satu perintah, dan memang begitu. Tool yang saya tulis untuk itu tiga baris, satu di antaranya shebang — baris pertama script yang memberi tahu sistem interpreter mana yang menjalankannya — satu lagi baris kosong. Yang membuatnya layak ditulis bukan panjangnya, melainkan banyaknya keputusan yang dipadatkan ke dalam satu baris tersisa — tiap keputusan punya alternatif masuk akal yang sempat saya pertimbangkan lalu saya tolak.
Gampang dirumuskan, canggung dibangun. Kecanggungannya bukan pada penggantiannya. Melainkan pada segala yang mengelilinginya: file mana yang didatangi, berapa proses yang dibayar untuk itu, apa yang terjadi pada file aslinya, dan apa yang tool ini janjikan tidak akan disentuh.
B. Untuk apa ia dipakai, dan untuk apa tidak
Dua situasi menjelaskan hampir seluruh pemakaian tool ini. Pertama seperti di atas: project hasil salinan template yang identitasnya perlu ditulis ulang sebelum commit pertama berisi pekerjaan sungguhan. Kedua lebih sempit dan muncul belakangan — domain berganti, atau prefix vendor berganti, dan string lamanya berserak di code yang tak seorang pun ingin periksa manual.
Keduanya berbagi satu bentuk yang pantas disebut namanya: penggantiannya mekanis. Tak ada penilaian yang diambil per kemunculan. Kalau keputusannya harus dibuat file demi file, tool ini salah pilihan, dan sebanyak apa pun flag ditambahkan takkan membuatnya jadi benar. Tool ini justru ada karena keputusannya sudah diambil sekali, di awal, dan kini tinggal diterapkan ke mana-mana tanpa ditimbang lagi di tiap tempat.
Ada satu lagi yang membuat seluruh pendekatan ini cukup aman untuk diperlakukan santai, dan ia sama sekali bukan bagian dari script: ia dijalankan di folder yang sudah di-commit. Kebiasaan tunggal itulah yang membuat tool ini boleh melewatkan setiap fitur pengaman yang mungkin ia butuhkan. Tak ada flag backup, tak ada mode dry-run, tak ada konfirmasi, sebab git diff menjawab pertanyaannya lebih baik ketimbang semua itu, dan git checkout . membatalkan seluruhnya lebih cepat dari waktu menjalankannya.
C. Enam hambatan yang menghalangi
Nama tak tinggal di satu tempat. Ia tersebar di source, config, template, dokumentasi, dan nama variabel, dan naluri mencarinya dengan membaca justru itulah yang gagal. Yang terlewat tidak mengumumkan diri saat penggantian; ia mengumumkan diri belakangan, ketika aplikasi berjalan dan sesuatu memanggil prefix yang sudah tak ada. Biaya kelewatan itu tertunda, dan itulah yang membuat pencarian manual terasa memadai — sampai ternyata tidak. Satu kemunculan yang lolos tak membatalkan rename; ia cuma menunda ongkosnya sampai ke tempat yang paling mahal, yaitu saat aplikasi sudah dianggap jadi.
"Replace All" di editor bukan operasi yang sama. Inilah alternatif paling jelas, dan pantas dikatakan terus terang kenapa saya menolaknya, bukan berpura-pura ia tak ada. Editor mengganti di seluruh yang sudah ia indeks — file yang terbuka, atau workspace yang pernah ia pindai. Folder yang disalin semenit lalu, atau subdirectory yang dikecualikan oleh setting yang tak seorang pun ingat pernah menulisnya, sederhananya berada di luar jangkauan. Hasilnya bukan error. Hasilnya keberhasilan separuh yang tampak utuh — dan keberhasilan separuh yang tampak utuh persis jenis kegagalan yang paling sulit ditemukan, sebab tak ada satu pun tanda yang memberi tahu bahwa masih ada sisa.
Memanggil sed sekali per file terlalu lambat untuk jadi default. Tool-nya dibangun dari dua perintah standar Unix: find, yang menelusuri folder dan mendaftar tiap file di dalamnya, dan sed, yang menulis ulang teks di dalam file dengan menerapkan substitusi seperti “ganti nama ini dengan nama itu”. Opsi -exec adalah cara find menyerahkan file yang ditemukannya ke perintah lain. Inilah beda -exec … {} + dengan -exec … {} \;, dan gampang dianggap sekadar rincian sintaks. Sintaks itu bukan rincian. Kedua bentuk itu berselisih dengan faktor yang bisa diukur, dan mengukurnya lebih meyakinkan daripada menjelaskannya.
Menyunting di tempat berarti tak ada langkah "salin dulu". sed -i menulis ulang file di posisinya. Tak ada file sementara yang mesti dipindahkan sesudahnya, tak ada folder keluaran yang harus dicocokkan. Itu keputusan nyata dengan akibat nyata, dan akibatnya adalah kebiasaan folder ter-commit di atas — jaring pengamannya sengaja ditaruh di luar tool.
Urutan argumen menentukan arahnya. <dari> <ke> <folder>. Tukar dua yang pertama dan penggantiannya berjalan terbalik, gampang dilakukan dan agak lebih sulit disadari. Urutannya bukan sembarang; ia dipilih supaya cocok dengan yang sudah dibaca di tempat lain.
Penggantiannya bekerja di level byte, dan itu disengaja. Ia peka huruf besar-kecil, tak peduli jenis file yang sedang dilihatnya, dan menolak mengikuti symlink. Ketiganya terdengar seperti tiga batasan terpisah. Ketiganya satu keputusan yang dilihat dari tiga sudut, dan masing-masing mencegah satu cara penggantian ini bisa keliru. Tool yang mengganti lebih banyak dari yang diminta jauh lebih berbahaya ketimbang tool yang mengganti lebih sedikit, sebab yang kurang akan ketahuan sedangkan yang berlebih menyelinap masuk ke commit.
Urutannya berarti di sini. Dua hambatan pertama menjelaskan kenapa sampai ada tool, bukan sekadar kebiasaan mencari dengan teliti. Ketiga dan keempat soal berapa ongkos perintahnya dan apa yang ia rusak. Dua terakhir soal presisi — apa yang tool janjikan berubah, dan apa yang ia janjikan dibiarkan. Penggantian yang cepat tapi tak presisi lebih buruk ketimbang tanpa tool sama sekali, sebab ia telanjur dipercaya. Pekerjaan manual selalu dicurigai dan karena itu diperiksa ulang; keluaran satu perintah yang berjalan sekejap justru jarang dibaca kembali, dan di situlah kekeliruan paling betah bersembunyi.
Metode
A. Satu baris, dan empat keputusan di dalamnya
Inilah jawaban atas hambatan pertama: saya berhenti mencari kemunculannya dan membiarkan filesystem yang mendaftarnya. Seluruh tool-nya satu baris.
Dibaca dari kiri ke kanan, ia empat keputusan yang berdiri berdampingan. find "$3" memutuskan di mana mencari. -type f memutuskan apa yang dihitung sebagai kandidat. -exec … {} + memutuskan bagaimana pekerjaannya dibatch. Dan sed -i "s|$1|$2|g" memutuskan apa arti mengganti. Masing-masing bisa diubah sendiri-sendiri, dan tiap alternatifnya lebih buruk karena alasan yang spesifik, bukan karena selera.
Yang jelas ia bukan pencarian. Tak ada daftar kecocokan untuk ditinjau, tak ada hitungan file terdampak, tak ada jeda sebelum perubahannya dikunci. Ketiadaan itu justru inti seksi berikutnya.
B. Kenapa filesystem, bukan editor
Ini menjawab hambatan kedua. Fitur replace-in-folder di editor ditopang sebuah indeks, dan indeks adalah model atas filesystem, bukan filesystem itu sendiri. Ia bisa basi, bisa separuh, dan bisa dikonfigurasi mengabaikan folder dengan cara yang tak terlihat pada saat penggantian dijalankan.
find tak punya indeks. Ia menyusuri pohonnya dan melaporkan yang ada di sana, sehingga folder yang disalin tiga puluh detik lalu diperlakukan persis sama dengan folder yang seminggu ini terbuka.
# what find actually visits — no index, no configuration, no exclusions
Perhatikan yang hilang dari daftar itu. Folder tersebut juga memuat satu symlink, dan ia tak muncul. Itu -type f sedang bekerja, dan itu hambatan keenam yang datang lebih awal — seksi presisi di bawah akan kembali ke sana.
C. Selisih antara + dan \;
Ini menjawab hambatan ketiga, dan inilah satu-satunya tempat di mana pengukuran menyelesaikan perdebatan yang tak bisa diselesaikan penjelasan. Kedua bentuk di bawah sama-sama benar. Keduanya menghasilkan keluaran identik. Bedanya cuma berapa kali sed dinyalakan.
# one sed per file — correct, and slow
# as many files per sed as the command line will hold — what the tool uses
Pada pohon berisi dua ribu file, selisihnya tidak halus. Menghitung langsung berapa proses yang dinyalakan memperlihatkan ke mana waktunya pergi.
|
|
Dua ribu satu proses berbanding tiga puluh empat. Bentuk \; menyalakan satu sed untuk tiap file dan membayar ongkos membuat proses, memuat binary-nya, mengompilasi ekspresinya, lalu merobohkan semuanya lagi — dua ribu kali, demi pekerjaan sungguhan yang tiap kalinya cuma beberapa milidetik. Bentuk + mengisi tiap command line dengan sebanyak mungkin path yang diizinkan sistem, lalu menyerahkan seluruh batch itu ke satu sed.
Tiga puluh lima kali lebih cepat, untuk perubahan satu karakter di perintahnya. Alasan mempedulikannya bukan dua detik yang dihemat pada satu kali jalan. Melainkan bahwa tool yang makan dua detik akan dipakai dengan santai, sementara tool yang makan dua menit di pohon besar akan dihindari dan diganti editor — padahal editor itulah yang hendak digantikan seluruh pendekatan ini.
D. Menyunting di tempat, dan di mana jaring pengamannya sebenarnya
Ini menjawab hambatan keempat. sed -i mengubah file di tempatnya berada, bukan menulis ke stdout untuk ditangkap perintah lain.
# without -i: the replacement goes to stdout, the file is untouched
# with -i: the file itself is rewritten, nothing is printed
Tanpa -i, penggantian sefolder butuh tempat menaruh hasilnya — pohon folder paralel, atau satu file sementara per input yang lalu mesti dipindahkan menimpa aslinya. Keduanya menambah bagian yang bergerak, dan keduanya melahirkan keadaan separuh jadi: sebagian file baru, sebagian masih lama.
Alternatif yang ditolak di sini sed -i.bak, yang menulis salinan cadangan di samping tiap file yang ia ubah. Itu cara paling jelas membuat suntingan di tempat bisa dibatalkan, dan saya memilih tidak memakainya, sebab ia meninggalkan satu file .bak di samping tiap file yang berubah di seluruh pohon — yang lalu mesti dicari dan dihapus, oleh perintah berjangkauan sama dan berisiko sama dengan yang melahirkannya.
Riwayat version control mengerjakan tugas yang sama dengan lebih baik:
Inilah kenapa menjalankannya di folder ter-commit bukan anjuran di README — itu rancangannya. Tool ini boleh sekecil ini justru karena pembatalnya sudah tersedia.
E. Urutan argumen, dipinjam bukan direka
Ini menjawab hambatan kelima. Pemanggilannya replace-kata.sh <dari> <ke> <folder>, dan pertanyaannya kenapa urutan itu, bukan misalnya folder lebih dulu.
# ^^^^^ ^^^^^^ ^^^^^^^^^
# from to where
# which reads the same way as the expression it builds:
# s|myapp|newapp|g
# ^^^^^ ^^^^^^
# from toUrutannya tidak saya rancang, melainkan saya pinjam. Sintaks substitusi milik sed sendiri dibaca dari lalu ke, dan siapa pun yang meraih tool ini hampir pasti pernah mengetik s|a|b|. Menyamainya membuat urutan argumen tak perlu diingat terpisah — ia dipanggil kembali dari yang sudah dikenal. Folder ditaruh terakhir sebab ia bagian yang paling mungkin berupa path panjang, dan argumen panjang lebih gampang disunting di ujung baris.
F. Yang ia janjikan tidak disentuh
Ini menjawab hambatan keenam, dan inilah yang menentukan tool ini bisa dipercaya atau tidak. Tiga perilaku, ketiganya akibat dari bekerja di atas byte ketimbang di atas code yang sudah diurai.
Ia peka huruf besar-kecil. Mengganti myapp jadi newapp tidak menyentuh MyappController.
# before
# after: replace-kata.sh myapp newapp ./project
Sekilas itu tampak seperti lubang. Justru sebaliknya. Nama class dan nilai config adalah dua identitas berbeda yang kebetulan berbagi huruf, dan penggantian yang diam-diam menulis ulang keduanya akan menghasilkan project dengan config yang benar tapi class yang tak lagi cocok dengan file tempatnya tinggal. Penggantian tanpa peka huruf adalah alternatif yang ditolak di sini, dan saya menolaknya sebab ia akan keliru dengan penuh percaya diri ketimbang jelas-jelas belum lengkap.
Ia tak memeriksa jenis file. Tiap file biasa di bawah target adalah kandidat, entah ia source, lockfile, atau artefak hasil kompilasi. Ini pantas dikatakan terus terang sebab ia menjelaskan bentuk seluruh pendekatannya: tool ini tak punya bayangan apa itu project. Ia melihat byte. Itulah kenapa ia bekerja pada template yang ditulis dalam bahasa yang belum pernah ia dengar, dan sekaligus kenapa ia diarahkan ke folder project, bukan ke home directory.
Ia tak mengikuti symlink. Ini -type f, dan inilah yang paling senyap di antara ketiganya.
# after the rename, the link is still a link
Tanpa -type f, find akan menyerahkan config.yml dan link.yml sekaligus ke sed, dan karena yang kedua menunjuk ke yang pertama, file yang sama akan disunting dua kali dalam satu jalan. Untuk penggantian biasa itu cuma mubazir. Untuk penggantian yang string barunya memuat string lamanya, itu bug kebenaran — pass kedua akan mengganti di dalam hasil pass pertama. Mengecualikan symlink melenyapkan seluruh kelas persoalan itu ketimbang menanganinya.
Hasil dan Pembahasan
A. Apa yang dibeli tiga baris
Saya tak pernah mengubah tool ini sejak menulisnya. Itulah keterangan paling berguna tentangnya: project hasil salinan template ditulis ulang identitasnya dalam satu perintah, dan langkah yang dulu memakan satu sore penuh grep-lalu-periksa kini cukup singkat sampai tak seorang pun menyusun rencana mengelilinginya.
Tabel di bawah adalah seluruh rancangannya, satu baris per keputusan, berikut alternatif yang tersedia di tiap titiknya.
| Keputusan | Yang dibeli | Alternatif yang ditolak |
|---|---|---|
find menyusuri pohon | tiap file, tak peduli apa yang sudah diindeks editor | "Replace All" editor — separuh, diam-diam |
-type f | tak ada file disunting dua kali lewat link | mengikuti link — bug kebenaran saat baru memuat lama |
-exec … + | 34 proses, bukan 2001; 0.07 detik, bukan 2.46 | -exec … \; — benar tapi 35× lebih lambat |
sed -i | tanpa pohon sementara, tanpa keadaan separuh pindah | -i.bak — meninggalkan sampah berjangkauan sama |
| peka huruf | MyappController selamat saat config diganti | tanpa peka huruf — keliru dengan percaya diri |
<dari> <ke> <folder> | urutan argumen dipanggil kembali dari sed | folder dulu — satu lagi yang mesti dihafal |
B. Bagian yang tidak ada di dalam script
Lima dari enam baris itu keputusan di dalam satu baris code. Yang keenam sama sekali bukan isi file — ia kebiasaan menjalankannya di folder ter-commit, dan itulah yang membuat suntingan di tempat yang tak bisa dibatalkan jadi default yang bisa diterima.
Itu pola umum yang pantas dibawa pulang dari tool tiga baris. Script-nya kecil karena keadaan di sekelilingnya menyerap tanggung jawab yang jika tidak harus ia pikul sendiri. Tambahkan flag backup, dan ia harus memutuskan backup-nya ditaruh di mana, kedaluwarsa kapan, dan apa yang terjadi bila sudah ada. Tambahkan mode dry-run, dan ia harus menyusun laporan perubahan yang diniatkan — yang pada titik itu cuma versi lebih buruk dari git diff. Tiap fitur yang tak ia punya adalah fitur yang sudah dikerjakan lebih baik oleh bagian lain dari alur kerjanya.
Kebalikannya juga benar dan pantas disebut sebagai batas, bukan sebagai kemenangan: jalankan ia di folder yang tak berada di bawah version control, dan tiap fitur yang absen itu berubah jadi lubang sungguhan. Tool ini tidak aman dengan sendirinya. Ia aman di tempat ia dipakai, dan itu klaim yang berbeda sekaligus lebih rapuh.
C. Ketika penggantian salah bentuk
Uji penggantian-mekanis dari pendahuluan ternyata jadi batasnya yang jujur. Mengganti nama aplikasi di seluruh template bekerja sebab string yang sama berarti sama di tiap tempat ia muncul. Penggantian yang menuntut penilaian — ketika user mestinya jadi account di domain model tapi tetap user di middleware authentication — sama sekali bukan operasi ini, dan meraih tool ini di sana menghasilkan diff yang lebih lama ditinjau ketimbang menggantinya manual sejak awal.
Batas itu bukan cacat pada tool-nya. Itu justru alasan tool ini boleh tiga baris: ia menyelesaikan kasus yang keputusannya sudah diambil, dan menolak jadi lebih dari itu.
Simpulan
Identitas aplikasi tersebar sejak konstruksinya — melintasi config, source, template, dan dokumentasi — dan menyalin project berarti mewarisi seluruhnya. Menggantinya dalam satu jalan cukup satu perintah, dan nilai perintah itu ada pada enam keputusan, bukan pada panjangnya: susuri filesystem ketimbang indeks, lewati symlink, batch pekerjaannya jadi segelintir proses ketimbang ribuan, sunting di tempat, pinjam urutan argumen dari sed, dan cocokkan byte secara persis.
Pengukurannya bagian yang pantas disimpan. + berbanding \; cuma satu karakter, dan pada dua ribu file ia jadi tiga puluh empat proses berbanding dua ribu satu, 0,07 detik berbanding 2,46. Tool yang cukup cepat sampai terasa gratis akan dipakai; yang makan dua menit akan digantikan editor yang justru ingin ia hindari.
Dan keputusan terakhirnya tidak ada di dalam script. Menjalankannya di folder ter-commit itulah yang membuat suntingan tak terbatalkan boleh jadi default, dan yang membuat saya berani meninggalkan tiap fitur pengaman dengan alasan version control sudah mengerjakannya lebih baik.
Istilah
- suntingan di tempat
- Menulis ulang file di posisinya sekarang, ketimbang menghasilkan keluaran yang mesti dipindahkan perintah lain.
sed -i. -exec … +- Bentuk
-execmilikfindyang menempelkan sebanyak mungkin path hasil pencocokan ke satu pemanggilan, ketimbang menjalankan perintahnya sekali per kecocokan. - file biasa
- Yang dipilih
-type f: file ordiner, mengecualikan folder, symlink, socket, dan device node. - ekspresi substitusi
s|dari|ke|g— instruksi ganti miliksed. Karakter sesudahsadalah pemisahnya, dangdi ekor menerapkannya ke tiap kemunculan dalam satu baris, bukan cuma yang pertama.- prefix tabel
- String yang ditempelkan di depan tiap nama tabel database milik satu aplikasi, supaya beberapa aplikasi bisa berbagi satu database tanpa bertabrakan.
- indeks (editor)
- Model atas file-file project yang dibangun editor di muka supaya pencarian jadi cepat. Berbeda dari filesystem, dan bisa basi atau separuh.
Technologies covered
- Bash
- sed