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.

pengguna164014
sumber

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
Thomas Erker
sumber
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.

Matijs
sumber
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
John B
sumber
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.

Sdlion
sumber
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

Varun Risbud
sumber
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