Cara mengganti nama banyak file menggunakan find


37

Saya ingin mengganti nama banyak file (file1 ... filen ke file1_renamed ... filen_renamed) menggunakan perintah findperintah:

find . -type f -name 'file*' -exec mv filename='{}' $(basename $filename)_renamed ';'

Tetapi mendapatkan kesalahan ini:

mv: cannot stat filename=./file1’: No such file or directory

Ini tidak berfungsi karena nama file tidak diartikan sebagai variabel shell.

Jawaban:


46

Berikut ini adalah perbaikan langsung dari pendekatan Anda:

find . -type f -name 'file*' -exec sh -c 'x="{}"; mv "$x" "${x}_renamed"' \;

Namun, ini sangat mahal jika Anda memiliki banyak file yang cocok, karena Anda memulai shell baru (yang mengeksekusi a mv) untuk setiap pertandingan. Dan jika Anda memiliki karakter lucu dalam nama file apa pun, ini akan meledak. Pendekatan yang lebih efisien dan aman adalah ini:

find . -type f -name 'file*' -print0 | xargs --null -I{} mv {} {}_renamed

Ini juga memiliki keuntungan bekerja dengan file yang anehnya bernama. Jika findmendukungnya, ini dapat dikurangi menjadi

find . -type f -name 'file*' -exec mv {} {}_renamed \;

The xargsVersi ini berguna bila tidak menggunakan {}, seperti di

find .... -print0 | xargs --null rm

Di sini rmdipanggil sekali (atau dengan banyak file beberapa kali), tetapi tidak untuk setiap file.

Saya menghapus basenamepertanyaan Anda, karena mungkin salah: Anda akan pindah foo/bar/file8ke file8_renamed, bukan foo/bar/file8_renamed.

Suntingan (seperti yang disarankan dalam komentar):

  • Ditambahkan pendek findtanpaxargs
  • Menambahkan stiker keamanan

Dalam hal xini tidak berguna: find . -type f -name 'file*' -exec mv {} "{}_renamed" \; xargsversi memiliki efisiensi yang sama seperti contoh pertama /
Costas

Apakah xada hanya untuk memperbaiki secara langsung pendekatan si penanya.
Thomas Erker

2
(1) Sangat berbahaya untuk digunakan {}secara langsung dalam perintah shell ( sh -c "…") - Anda harus selalu menyampaikannya sebagai argumen. (2) Tidak semua versi findmendukung {}_renamedkonstruk. (3) Saya tidak mengerti pernyataan Anda yang xargsberguna untuk menghapus file (berbeda dengan mengganti nama mereka).
G-Man Mengatakan 'Reinstate Monica'

@ G-Man: Perbedaan dengan xargsbukan mvvs rm, tetapi penggunaan {}vs tanpa. Yang pertama mirip dengan mv file1 file1_renamed; mv file2 file2_renamedyang kedua rm file1 file2.
Thomas Erker

1
dan jika Anda ingin mengubah file _renamed kembali ke nama aslinya, bagaimana Anda melakukannya?
mcmillab

17

Setelah mencoba jawaban pertama dan mempermainkannya sedikit saya menemukan bahwa itu bisa dilakukan sedikit lebih pendek dan kurang rumit menggunakan -execdir:

find . -type f -name 'file*' -execdir mv {} {}_renamed ';'

Sepertinya itu juga harus melakukan apa yang Anda butuhkan.


2
Dengan findimplementasi yang mendukung -execdirdan {}tidak secara keseluruhan, itu juga yang paling aman. Anda mungkin ingin menambahkan a -ito mv(dan -Tjika Anda mvmendukungnya)
Stéphane Chazelas

1
@ StéphaneChazelas bahkan lebih baik, daripada mengandalkan mvuntuk meminta, atau selain itu, Anda dapat (tidak diragukan tergantung pada apakah implementasi Anda findmendukungnya) juga menggunakan -okdiryang akan menampilkan perintah yang akan dieksekusi sebelum menjalankannya.
Matijs

Layak disebutkan itu -depthjuga merupakan ide bagus jika Anda juga akan menyentuh nama direktori.
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Argh, baru saja menemukan bahwa -execdirmemang memiliki satu kelemahan yang sangat menjengkelkan, findmenolak untuk melakukan apa pun jika PATHmengandung jalur relatif ... askubuntu.com/questions/621132/… find: The relative path XXX is included in the PATH environment
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

6

Pendekatan lain adalah dengan menggunakan while readloop over findoutput. Hal ini memungkinkan akses ke setiap nama file sebagai variabel yang dapat dimanipulasi tanpa harus khawatir tentang biaya / potensi masalah keamanan tambahan pemijahan terpisah sh -cproses menggunakan find's -execpilihan.

find . -type f -name 'file*' |
    while IFS= read file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done

Dan jika shell yang digunakan mendukung -dopsi untuk menentukan readpembatas, Anda dapat mendukung file dengan nama yang aneh (misalnya dengan baris baru) menggunakan yang berikut:

find . -type f -name 'file*' -print0 |
    while IFS= read -d '' file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done

3

Saya ingin memperluas pada jawaban pertama dan perhatikan bahwa ini tidak akan berfungsi untuk menambahkan ke nama file karena ./awalan path hadir dalam argumen nama file.

Memodifikasi jawaban Thomas Erker, saya menemukan ini pendekatan yang lebih umum

find . -name PATTERN -printf "%f\0" | xargs --null -I{} mv {} "prefix {} suffix"

opsi xargs:

--nullMenunjukkan bahwa setiap argumen yang melewati stdindiakhiri dengan karakter nol ( \0). Dengan cara ini nama file dapat berisi spasi, jika tidak setiap kata akan dimasukkan sebagai parameter yang berbeda untuk mvperintah.

-I replace-strSetiap arus replace-strakan diganti dengan argumen yang dibaca stdin. Jadi, Anda dapat mengubahnya untuk string lain jika Anda membutuhkannya.


Kenapa harus pringf "%f\0"ganti a print0?
slm

1
@sim, karena -print0akan menghasilkan ./awalan jika PATTERNberisi karakter meta shell yang menghalangi ketika mengganti nama menjadi sesuatu yang awalan nama aslinya. (mis. ganti nama 0 - foo.txtmenjadi 00 - foo.txt, 1 - bar.txtke 01 - bar.txtdll.)
das-g

2

Saya bisa melakukan sesuatu yang mirip dengan for, find, dan mv.

for i in $(find . -name 'config.yml'); do mv $i $i.bak; done

Ini menemukan semua config.ymlfile dan menamainya kembaliconfig.yml.bak


ini memiliki keuntungan karena dapat menggunakan ekspansi variabel mis. $ {i: r}. Jawaban bagus.
Salami

Ya, Anda bisa menaruh ekspresi apa pun di dalam fordan melakukan operasi massal.
Varun Risbud
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.