Ganti nama mengembalikan "bareword not allowed" ketika mencoba merender bagian dari beberapa nama file

12

Saya memiliki dua file dalam folder di Ubuntu 16.04 saya:

a1.dat
b1.DAT

Saya ingin mengubah nama b1.DATuntuk b1.datjadi saya harus mengikuti file sebagai hasil dalam folder:

a1.dat
b1.dat

Saya mencoba (tidak berhasil):

$ rename *.DAT *.dat
Bareword "b1" not allowed while "strict subs" in use at (user-supplied code).
Bareword "DAT" not allowed while "strict subs" in use at (user-supplied code).

dan

$ find . -iname "*.DAT" -exec rename DAT dat '{}' \;
Bareword "DAT" not allowed while "strict subs" in use at (user-supplied code).
Bareword "DAT" not allowed while "strict subs" in use at (user-supplied code).

Mencari ini tidak menghasilkan solusi yang berarti ...

Samo
sumber
Anda juga mungkin ingin belajar tentang mmv
PlasmaHH

Jawaban:

14

Kesalahan itu sepertinya berasal dari Perl rename. Anda perlu menggunakan kutipan, tetapi Anda hanya perlu menentukan bagian yang ingin Anda ubah, cari dan ganti gaya. Sintaksnya seperti ini:

rename -n 's/\.DAT/\.dat/' *

Hapus -nsetelah pengujian untuk benar-benar mengganti nama file.

Untuk memasukkan file tersembunyi, ubah pengaturan glob sebelum menjalankan perintah:

shopt -s dotglob

Jika Anda ingin mengganti nama file secara rekursif, Anda dapat menggunakan

shopt -s globstar
rename -n 's/\.DAT/\.dat/' **

atau jika ada banyak jalur di atau di bawah direktori saat ini yang tidak diakhiri .DAT, Anda sebaiknya menentukan jalur tersebut di perintah kedua:

rename -n 's/\.DAT/\.dat/' **/*.DAT

Ini akan lebih cepat jika file Anda memiliki berbagai nama berbeda yang tidak berakhir .DAT. [1]

Untuk mematikan pengaturan ini, Anda dapat menggunakan shopt -u, misalnya shopt -u globstar, tetapi mereka mati secara default dan akan dimatikan saat Anda membuka shell baru.

Jika ini menghasilkan daftar argumen yang terlalu panjang, Anda dapat menggunakan, misalnya find,:

find -type f -name "*.DAT" -exec rename -n -- 's/\.DAT/\.dat/' {} \;

atau lebih baik

find -type f -name "*.DAT" -exec rename -n -- 's/\.DAT/\.dat/' {} +

Menggunakan find ... -execdengan +lebih cepat daripada menggunakan \;karena membangun daftar argumen dari file yang ditemukan. Awalnya saya berpikir bahwa tidak mungkin bagi Anda untuk menggunakannya, karena Anda menyebutkan bahwa Anda memiliki argument list too longmasalah, tetapi sekarang saya tahu bahwa daftar itu juga akan secara cerdik dipecah menjadi beberapa pemanggilan perintah yang diperlukan untuk menghindari masalah itu. [2] .

Karena renameakan memproses setiap nama file dengan cara yang sama, tidak masalah berapa lama daftar argumen karena dapat dengan aman dibagi di beberapa pemanggilan. Jika perintah yang Anda gunakan -exectidak menerima beberapa argumen atau mengharuskan argumennya berada dalam urutan tertentu atau karena alasan lain memecah daftar argumen akan menyebabkan sesuatu yang tidak diinginkan terjadi, Anda dapat menggunakan \;, yang menyebabkan perintah dipanggil sekali untuk setiap file yang ditemukan (jika daftar argumen terlalu panjang untuk metode lain, ini akan memakan waktu lama!).


Terima kasih banyak kepada Eliah Kagan untuk saran yang sangat berguna untuk meningkatkan jawaban ini:

[1] Menentukan nama file saat globbing .
[2] find ... -execdengan +membagi daftar argumen .

Zanna
sumber
Terima kasih. Bagaimana cara melakukannya secara rekursif (untuk semua file di semua subfolder)?
Samo
@Samo lihat edit saya :)
Zanna
Tidak berfungsi: $ rename 's / \. DAT / \. Dat /' ** bash: / usr / bin / rename: Daftar argumen terlalu panjang
Samo
1
@ Samo Anda harus menghapus -ndari mengubah nama setelah pengujian - itu hanya untuk menunjukkan apa yang akan diubah
Zanna
1
Di Centos dan Arch, mengganti nama tidak memperlihatkan perilaku ini. Saya tiba di sini dari menggunakan Debian di WSL. Sintaks ini berhasil.
xtian
6

Anda dapat melakukan:

rename -n 's/DAT$/\L$&/' *.DAT

Jatuhkan -nagar penggantian nama sebenarnya terjadi.

  • Pola glob *.DATcocok dengan semua file yang berakhir di .DATdirektori saat ini

  • di renamesubstitusi, DAT$cocok DATdi akhir

  • \L$&membuat seluruh pertandingan lebih kecil; $&mengacu pada seluruh pertandingan

Jika Anda hanya ingin melakukannya untuk b1.DAT:

rename -n 's/DAT$/\L$&/' b1.DAT

Contoh:

% rename -n 's/DAT$/\L$&/' *.DAT
rename(b1.DAT, b1.dat)
heemayl
sumber
4

Jawaban lain telah membahas salah satu dari dua aspek utama dari pertanyaan ini: yaitu tentang bagaimana cara sukses menjalankan operasi penggantian nama yang Anda butuhkan. Tujuan dari jawaban ini adalah untuk menjelaskan mengapa perintah Anda tidak berfungsi, termasuk arti dari pesan kesalahan "bareword not allowed" yang aneh dalam konteks renameperintah.

Bagian pertama dari jawaban ini adalah tentang hubungan antara renamedan Perl dan bagaimana renamemenggunakan argumen baris perintah pertama yang Anda lewati, yang merupakan argumen kodenya. Bagian kedua adalah tentang bagaimana shell melakukan ekspansi - khususnya globbing - untuk membuat daftar argumen. Bagian ketiga adalah tentang apa yang terjadi dalam kode Perl yang memberikan kesalahan "Bareword not allowed". Akhirnya, bagian keempat adalah ringkasan dari semua langkah yang terjadi antara memasukkan perintah dan mendapatkan kesalahan.

1. Saat renamememberi Anda pesan kesalahan aneh, tambahkan "Perl" ke pencarian Anda.

Di Debian dan Ubuntu, renameperintahnya adalah skrip Perl yang melakukan penggantian nama file. Pada rilis yang lebih lama - termasuk 14,04 LTS, yang masih didukung sampai tulisan ini - itu adalah tautan simbolik yang menunjuk ( secara tidak langsung ) ke prenameperintah. Pada rilis yang lebih baru, alih-alih menunjuk ke file-renameperintah yang lebih baru . Mereka dua perintah rename Perl bekerja sebagian besar dengan cara yang sama, dan saya hanya akan merujuk kepada mereka baik sebagai renameuntuk sisa jawaban ini.

Saat Anda menggunakan renameperintah, Anda tidak hanya menjalankan kode Perl yang ditulis orang lain. Anda juga menulis kode Perl Anda sendiri dan menyuruhnya renamemenjalankannya. Ini karena argumen baris perintah pertama yang Anda berikan ke perintah, selain argumen opsi seperti , terdiri dari kode Perl aktualrename-n . The renameperintah menggunakan kode ini untuk beroperasi pada masing-masing nama path yang Anda lulus sebagai argumen baris perintah berikutnya. (Jika Anda tidak memberikan argumen nama path, maka renamebacalah nama path dari input standar , satu argumen per baris.)

Kode ini dijalankan dalam loop , yang iterates sekali per pathname. Di bagian atas setiap iterasi dari loop, sebelum kode Anda berjalan, $_variabel khusus diberikan pathname yang sedang diproses. Jika kode Anda menyebabkan nilai $_diubah menjadi sesuatu yang lain, maka file itu diganti namanya untuk memiliki nama baru.

Banyak ekspresi dalam Perl beroperasi secara implisit pada $_variabel ketika mereka tidak diberi ekspresi lain untuk digunakan sebagai operan . Sebagai contoh, ekspresi substitusi $str =~ s/foo/bar/perubahan kejadian pertama foodalam string yang diselenggarakan oleh $str variabel untuk bar, atau daun itu tidak berubah jika tidak mengandung foo. Jika Anda hanya menulis s/foo/bar/tanpa secara eksplisit menggunakan satu =~operator yang , kemudian beroperasi pada $_. Ini adalah s/foo/bar/kependekan dari $_ =~ s/foo/bar/.

Ini umum untuk lulus suatu s///ekspresi untuk renamesebagai argumen kode (yaitu, baris perintah argumen pertama), tetapi Anda tidak perlu. Anda dapat memberikan kode Perl apa pun yang Anda inginkan agar dijalankan di dalam loop, untuk memeriksa setiap nilai $_dan (secara kondisional) memodifikasinya.

Ini memiliki banyak konsekuensi keren dan berguna , tetapi sebagian besar dari mereka jauh di luar cakupan pertanyaan dan jawaban ini. Alasan utama saya membawa ini ke sini - pada kenyataannya, alasan utama saya memutuskan untuk mengirim jawaban ini - adalah untuk membuat titik itu, karena argumen pertama renameadalah kode Perl, kapan saja Anda mendapatkan pesan kesalahan aneh dan Anda kesulitan menemukan informasi tentang itu dengan mencari, Anda dapat menambahkan "Perl" ke string pencarian (atau bahkan mengganti "ganti nama" dengan "Perl", kadang-kadang) dan Anda akan sering menemukan jawaban.

2. Dengan rename *.DAT *.dat, renameperintah tidak pernah melihat *.DAT!

Perintah seperti rename s/foo/bar/ *.txtbiasanya tidak lulus *.txtsebagai argumen baris perintah ke renameprogram, dan Anda tidak menginginkannya , kecuali jika Anda memiliki file yang namanya secara harfiah *.txt, yang semoga tidak Anda lakukan.

renametidak menafsirkan pola gumpal seperti *.txt, *.DAT, *.dat, x*, *y, atau *ketika berlalu untuk itu sebagai argumen pathname. Sebagai gantinya, shell Anda melakukan ekspansi pathname pada mereka (yang juga disebut ekspansi nama file, dan juga disebut globbing). Ini terjadi sebelum renameutilitas dijalankan. Shell memperluas gumpalan ke beberapa nama path yang berpotensi dan melewati semuanya, sebagai argumen baris perintah terpisah, untuk rename. Di Ubuntu, shell interaktif Anda adalah Bash , kecuali Anda telah mengubahnya, itulah sebabnya saya ditautkan ke manual referensi Bash di atas.

Ada satu situasi di mana pola gumpalan dapat dilewatkan sebagai argumen baris perintah tunggal yang tidak diperluas untuk rename: ketika tidak cocok dengan file apa pun. Kerang yang berbeda menunjukkan perilaku default yang berbeda dalam situasi ini, tetapi perilaku default Bash adalah untuk hanya lulus bola secara harfiah. Namun, Anda jarang menginginkan ini! Jika Anda menginginkannya, maka Anda harus memastikan bahwa polanya tidak diperluas, dengan mengutipnya . Ini berlaku untuk meneruskan argumen ke perintah apa pun, bukan hanya untuk rename.

Mengutip tidak hanya untuk globbing (ekspansi nama file), karena ada ekspansi lain yang dilakukan shell Anda pada teks yang tidak dikutip dan, untuk sebagian dari mereka tetapi tidak pada yang lain , juga pada teks yang dilampirkan dalam " "tanda kutip. Secara umum, kapan saja Anda ingin memberikan argumen yang berisi karakter yang dapat diperlakukan secara khusus oleh shell, termasuk spasi, Anda harus mengutipnya, lebih disukai dengan ' 'kutipan .

Kode Perl s/foo/bar/tidak mengandung apa pun yang diperlakukan secara khusus oleh shell, tetapi itu akan menjadi ide bagus bagi saya untuk mengutipnya juga - dan ditulis 's/foo/bar/'. (Bahkan, satu-satunya alasan aku tidak adalah bahwa itu akan membingungkan untuk beberapa pembaca, karena saya belum berbicara tentang mengutip.) Alasan saya mengatakan ini akan menjadi baik karena itu sangat umum bahwa kode Perl tidak mengandung karakter seperti itu, dan jika saya mengubah kode itu, saya mungkin tidak ingat untuk memeriksa apakah kuotasi diperlukan. Sebaliknya, jika Anda ingin shell untuk memperluas bola, itu tidak harus dikutip.

3. Apa yang dimaksud penerjemah Perl dengan "bareword not allowed"

Pesan kesalahan yang Anda tunjukkan dalam pertanyaan Anda mengungkapkan bahwa, ketika Anda berlari rename *.DAT *.dat, shell Anda diperluas *.DATke daftar satu atau lebih nama file, dan bahwa yang pertama dari nama file itu b1.DAT. Semua argumen selanjutnya - baik yang lain berkembang dari *.DATdan yang diperluas dari - *.datdatang setelah argumen itu, sehingga mereka akan ditafsirkan sebagai nama path.

Karena apa yang benar-benar dijalankan adalah sesuatu seperti rename b1.DAT ..., dan karena renamememperlakukan argumen non-opsi pertamanya sebagai kode Perl, pertanyaannya menjadi: mengapa b1.DATkesalahan "bareword not allowed" ini muncul ketika Anda menjalankannya sebagai kode Perl?

Bareword "b1" not allowed while "strict subs" in use at (user-supplied code).
Bareword "DAT" not allowed while "strict subs" in use at (user-supplied code).

Dalam sebuah shell, kami mengutip string kami untuk melindunginya dari ekspansi shell yang tidak disengaja yang sebaliknya akan mengubahnya menjadi string lain secara otomatis (lihat bagian di atas). Shell adalah bahasa pemrograman tujuan khusus yang bekerja sangat berbeda dari bahasa tujuan umum (dan sintaksis dan semantik yang sangat aneh mencerminkannya). Tetapi Perl adalah bahasa pemrograman tujuan umum dan, seperti kebanyakan bahasa pemrograman tujuan umum, tujuan utama mengutip dalam Perl bukan untuk melindungi string, tetapi untuk menyebutkannya sama sekali. Ini sebenarnya cara sebagian besar bahasa pemrograman mirip dengan bahasa alami. Dalam bahasa Inggris, dan seandainya Anda memiliki anjing, "anjing Anda" adalah frasa dua kata, sedangkan anjing Anda adalah anjing. Demikian pula, dalam Perl, '$foo'adalah string, sedangkan $fooadalah sesuatu yang namanya $foo.

Namun, tidak seperti hampir semua bahasa pemrograman tujuan umum lainnya, Perl juga terkadang akan menafsirkan teks yang tidak dikutip sebagai menyebutkan string - string yang "sama" dengan itu, dalam arti bahwa itu terdiri dari karakter yang sama dalam urutan yang sama. Itu hanya akan mencoba menafsirkan kode seperti itu jika itu adalah kata kunci (tidak $atau sigil lainnya, lihat di bawah), dan setelah itu tidak dapat menemukan makna lain untuk memberikannya. Maka itu akan menganggapnya sebagai string, kecuali Anda mengatakannya tidak dengan mengaktifkan pembatasan .

Variabel dalam Perl biasanya dimulai dengan karakter tanda baca yang disebut sigil , yang menentukan tipe luas dari variabel. Misalnya, $berarti skalar , @berarti array , dan %berarti hash . ( Ada yang lain. ) Jangan khawatir jika Anda merasa membingungkan (atau membosankan), karena saya hanya akan mengatakan bahwa ketika nama yang valid muncul dalam program Perl tetapi tidak didahului oleh sigil, nama itu dikatakan sebagai kata pengantar .

Barewords melayani berbagai tujuan, tetapi mereka biasanya menandakan fungsi built-in atau subrutin yang ditentukan pengguna yang telah didefinisikan dalam program (atau dalam modul yang digunakan oleh program). Perl tidak memiliki fungsi bawaan yang disebut b1atau DAT, jadi ketika penerjemah Perl melihat kode b1.DAT, ia mencoba memperlakukan b1dan DATsebagai nama subrutin. Dengan asumsi tidak ada subrutin seperti itu telah didefinisikan, ini gagal. Kemudian, asalkan pembatasan belum diaktifkan, itu memperlakukan mereka sebagai string. Ini akan berhasil, meskipun apakah Anda benar-benar ingin ini terjadi atau tidak, adalah dugaan siapa pun. Perl .Operator merangkai string , sehingga b1.DATmengevaluasi ke stringb1DAT. Artinya, b1.DATadalah cara yang buruk untuk menulis sesuatu seperti 'b1' . 'DAT'atau "b1" . "DAT".

Anda dapat menguji ini sendiri dengan menjalankan perintah perl -E 'say b1.DAT', yang meneruskan skrip Perl pendek say b1.DATke penerjemah Perl, yang menjalankannya, mencetak b1DAT. (Dalam perintah itu, ' 'kutipan memberitahu shell untuk lulus say b1.DATsebagai argumen baris perintah tunggal, jika tidak, ruang akan menyebabkan saydan b1.DATakan diuraikan sebagai kata yang terpisah dan perlakan menerima mereka sebagai argumen yang terpisah. perlTidak tidak melihat tanda kutip sendiri, karena yang shell menghapusnya .)

Tapi sekarang, coba tulisuse strict; di skrip Perl sebelumnya say. Sekarang gagal dengan jenis kesalahan yang sama yang Anda dapatkan dari rename:

$ perl -E 'use strict; say b1.DAT'
Bareword "b1" not allowed while "strict subs" in use at -e line 1.
Bareword "DAT" not allowed while "strict subs" in use at -e line 1.
Execution of -e aborted due to compilation errors.

Ini terjadi karena use strict;melarang penerjemah Perl memperlakukan kata kunci bar sebagai string. Untuk melarang fitur khusus ini, itu benar-benar cukup untuk mengaktifkan subspembatasan saja. Perintah ini menghasilkan kesalahan yang sama seperti di atas:

perl -E 'use strict "subs"; say b1.DAT'

Tetapi biasanya programmer Perl hanya akan menulis use strict;, yang memungkinkan subspembatasan dan dua lainnya. use strict;umumnya direkomendasikan sebagai latihan. Jadi renameperintah melakukan ini untuk kode Anda . Itu sebabnya Anda mendapatkan pesan kesalahan itu.

4. Singkatnya, inilah yang terjadi:

  1. Shell Anda dilewati b1.DATsebagai argumen baris perintah pertama, yang renamediperlakukan sebagai kode Perl untuk dijalankan dalam satu loop untuk setiap argumen pathname.
  2. Ini diartikan b1dan DATdihubungkan dengan .operator.
  3. b1dan DATtidak diawali dengan sigils, jadi mereka diperlakukan sebagai kata pengantar.
  4. Kedua kata kunci itu akan dianggap sebagai nama fungsi bawaan atau subrutin yang ditentukan pengguna, tetapi tidak ada yang memiliki nama tersebut.
  5. Jika "subs ketat" tidak diaktifkan, maka mereka akan diperlakukan seperti ekspresi string 'b1'dan 'DAT'dan disatukan. Itu jauh dari yang Anda maksudkan, yang menerangi bagaimana fitur ini seringkali tidak membantu.
  6. Tapi "subs ketat" diaktifkan, karena renamememungkinkan semua pembatasan ( vars, refs, dan subs). Karenanya, Anda malah mendapatkan kesalahan.
  7. renameberhenti karena kesalahan ini. Karena kesalahan semacam ini terjadi lebih awal, tidak ada upaya penggantian nama file yang dilakukan, meskipun Anda belum lulus -n. Ini adalah hal yang baik, yang sering melindungi pengguna dari perubahan nama file yang tidak disengaja dan kadang-kadang bahkan dari kehilangan data aktual.

Terima kasih kepada Zanna , yang membantu saya mengatasi beberapa kekurangan penting dalam draft jawaban ini yang lebih awal . Tanpa dia, jawaban ini akan menjadi jauh lebih masuk akal, dan mungkin tidak diposting sama sekali.

Eliah Kagan
sumber