Bagaimana cara mengubah ekstensi beberapa file secara rekursif dari baris perintah?

73

Saya memiliki banyak file dengan .abcekstensi dan ingin mengubahnya ke .edefg
Bagaimana melakukan ini dari baris perintah?

Saya memiliki folder root dengan banyak sub-folder, jadi solusinya harus bekerja secara rekursif.

tommyk
sumber

Jawaban:

85

Cara portabel (yang akan bekerja pada sistem yang mendukung POSIX):

find /the/path -depth -name "*.abc" -exec sh -c 'mv "$1" "${1%.abc}.edefg"' _ {} \;

Di bash4, Anda dapat menggunakan globstar untuk mendapatkan gumpalan rekursif (**):

shopt -s globstar
for file in /the/path/**/*.abc; do
  mv "$file" "${file%.abc}.edefg"
done

Perintah (perl) renamedi Ubuntu dapat mengganti nama file menggunakan sintaks ekspresi reguler perl, yang dapat Anda gabungkan dengan globstar atau find:

# Using globstar
shopt -s globstar
files=(/the/path/**/*.abc)  

# Best to process the files in chunks to avoid exceeding the maximum argument 
# length. 100 at a time is probably good enough. 
# See http://mywiki.wooledge.org/BashFAQ/095
for ((i = 0; i < ${#files[@]}; i += 100)); do
  rename 's/\.abc$/.edefg/' "${files[@]:i:100}"
done

# Using find:
find /the/path -depth -name "*.abc" -exec rename 's/\.abc$/.edefg/' {} +

Juga lihat http://mywiki.wooledge.org/BashFAQ/030

geirha
sumber
yang pertama hanya menambahkan ekstensi baru ke yang lama, itu tidak menggantikan ...
user2757729
3
@ user2757729, ia menggantinya. "${1%.abc}"meluas ke nilai $1kecuali tanpa .abcdi akhir. Lihat faq 100 untuk informasi lebih lanjut tentang manipulasi string shell.
geirha
Saya menjalankan ini pada struktur file file ~ 70k ... setidaknya pada shell saya itu pasti tidak menggantinya.
user2757729
1
@ user2757729 Anda mungkin meninggalkan "abc" di${1%.abc}...
kvnn
1
Jika ada orang lain yang bertanya-tanya apa yang garis bawah lakukan di perintah pertama, itu diperlukan karena dengan sh -c argumen pertama ditugaskan ke $ 0 dan argumen yang tersisa ditugaskan ke parameter posisi . Jadi _argumen dummy, dan '{}' adalah argumen posisi pertama sh -c.
hellobenallan
48

Ini akan melakukan tugas yang diperlukan jika semua file ada di folder yang sama

rename 's/.abc$/.edefg/' *.abc

Untuk mengganti nama file secara rekursif, gunakan ini:

find /path/to/root/folder -type f -name '*.abc' -print0 | xargs -0 rename 's/.abc$/.edefg/'
Cakra
sumber
8
Atau rename 's/.abc$/.edefg/' /path/to/root/folder/**/*.abcdalam versi modern Bash.
Adam Byrtek
Terima kasih banyak, Adam, karena memberi saya tip tentang cara menggunakan * .abc dalam folder secara rekursif!
Rafał Cieślak
Tip yang bagus, terima kasih! Di mana saya dapat menemukan lebih banyak dokumentasi tentang sintaks regex? Seperti apa sdi awal, dan opsi apa yang bisa saya gunakan di sana. Terima kasih!
Anto
@AdamByrtek Saya baru saja gagal dengan saran Anda tentang Utopic. ('tidak ada file seperti itu' atau semacamnya.)
Jonathan Y.
14

Satu masalah dengan pengubahan nama rekursif adalah bahwa metode apa pun yang Anda gunakan untuk mencari file, ia meneruskan seluruh path rename, bukan hanya nama file. Itu membuatnya sulit untuk melakukan penggantian nama yang kompleks di folder bersarang.

Saya menggunakan find's -execdirtindakan untuk memecahkan masalah ini. Jika Anda menggunakan -execdiralih-alih -exec, perintah yang ditentukan dijalankan dari subdirektori yang berisi file yang cocok. Jadi, alih-alih melewati seluruh jalan menuju rename, itu hanya melewati ./filename. Itu membuatnya lebih mudah untuk menulis regex.

find /the/path -type f \
               -name '*.abc' \
               -execdir rename 's/\.\/(.+)\.abc$/version1_$1.abc/' '{}' \;

Secara terperinci:

  • -type f berarti hanya mencari file, bukan direktori
  • -name '*.abc' berarti hanya cocok dengan nama file yang berakhiran .abc
  • '{}'adalah placeholder yang menandai tempat di mana -execdirakan memasukkan jalan yang ditemukan. Kutipan tunggal diperlukan, untuk memungkinkannya menangani nama file dengan spasi dan karakter shell.
  • Garis miring terbalik setelah -typedan -nameadalah karakter lanjutan garis bash. Saya menggunakannya untuk membuat contoh ini lebih mudah dibaca, tetapi mereka tidak diperlukan jika Anda menempatkan perintah Anda semua pada satu baris.
  • Namun, garis miring terbalik di akhir -execdirbaris diperlukan. Itu ada untuk melarikan diri dari titik koma, yang mengakhiri perintah dijalankan oleh -execdir. Menyenangkan!

Penjelasan regex:

  • s/ mulai dari regex
  • \.\/cocok dengan yang terdepan ./ yang -execdir lewati. Gunakan \ untuk menghindari. dan / metakarakter (catatan: bagian ini bervariasi tergantung pada versi Anda find. Lihat komentar dari pengguna @apollo)
  • (.+)cocok dengan nama file. Tanda kurung menangkap pertandingan untuk digunakan nanti
  • \.abc lepas titik, cocok dengan abc
  • $ jangkar korek api di ujung senar

  • / menandai akhir bagian "cocok" dari regex, dan awal bagian "ganti"

  • version1_ tambahkan teks ini ke setiap nama file

  • $1referensi nama file yang ada, karena kami menangkapnya dengan tanda kurung. Jika Anda menggunakan beberapa set tanda kurung di bagian "cocok", Anda dapat merujuknya di sini menggunakan $ 2, $ 3, dll.
  • .abcnama file baru akan berakhiran .abc. Tidak perlu keluar dari metacharacter titik di sini di bagian "ganti"
  • / akhir regex

Sebelum

tree --charset=ascii

|-- a_file.abc
|-- Another.abc
|-- Not_this.def
`-- dir1
    `-- nested_file.abc

Setelah

tree --charset=ascii

|-- version1_a_file.abc
|-- version1_Another.abc
|-- Not_this.def
`-- dir1
    `-- version1_nested_file.abc

Petunjuk: renameopsi -n berguna. Itu menjalankan kering dan menunjukkan kepada Anda apa nama itu akan berubah, tetapi tidak membuat perubahan.

Christian Long
sumber
Terima kasih atas penjelasan detailnya, tetapi anehnya, ketika saya mencoba ini, --execdirtidak lulus dalam memimpin ./sehingga \.\/bagian dalam regex dapat dihilangkan. Mungkin itu karena saya menggunakan OSX bukan ubuntu
apollo
5

Cara portabel lainnya:

find /the/path -depth -type f -name "*.abc" -exec sh -c 'mv -- "$1" "$(dirname "$1")/$(basename "$1" .abc).edefg"' _ '{}' \;
aNeutrino
sumber
4
Selamat Datang di Tanya Ubuntu! Meskipun ini adalah jawaban yang berharga, saya sarankan untuk meluaskannya (dengan mengedit) untuk menjelaskan bagaimana dan mengapa perintah itu bekerja.
Eliah Kagan
0

Inilah yang saya lakukan dan bekerja persis seperti yang saya inginkan. Saya menggunakan mvperintah. Saya memiliki banyak .3gpfile dan saya ingin mengubah nama semuanya.mp4

Berikut adalah daftar singkat untuk itu:

for i in *.3gp; do mv -- "$i" "ren-$i.mp4"; done

Yang cukup memindai melalui direktori saat ini, mengambil semua file .3gp, lalu mengganti nama (menggunakan mv) ke ren-name_of_file.mp4

KhoPhi
sumber
Itu akan berganti nama file.3gpmenjadi file.3gp.mp4, yang mungkin bukan yang diinginkan kebanyakan orang.
muru
@uru tetapi prinsipnya masih ada. Cukup pilih ekstensi apa pun lalu tentukan ekstensi tujuan untuk mendapatkan namanya kembali. The .3gpatau .mp4di sini hanya untuk tujuan ilustrasi.
KhoPhi
Sintaks lebih disukai, satu-liner dan mudah dimengerti. Namun, saya akan menggunakan basename "$i" .mp4untuk menghapus ekstensi sebelumnya, bukan "ren- $ i.mp4".
TaoPR
Benar. Poin bagus.
KhoPhi
0

Saya menemukan cara mudah untuk mencapai ini. Untuk mengubah ekstensi banyak file dari jpg ke pdf, gunakan:

for file in /path/to; do mv $file $(basename -s jpg $file)pdf ; done
Tenang
sumber
0

Saya akan menggunakan perintah mmv dari paket dengan nama yang sama :

mmv ';*.abc' '#1#2.edefg'

The ;pertandingan nol atau lebih */dan sesuai dengan #1di penggantian. The *bersesuaian untuk #2. Versi non-rekursif akan menjadi

mmv '*.abc' '#1.edefg'
MvG
sumber
0

Ganti nama file dan direktori dengan find -execdir | rename

Jika Anda akan mengubah nama file dan direktori tidak hanya dengan akhiran, maka ini adalah pola yang baik:

PATH="$(echo "$PATH" | sed -E 's/(^|:)[^\/][^:]*//g')" \
  find . -depth -execdir rename 's/findme/replaceme/' '{}' \;

-execdirOpsi yang luar biasa melakukan a cdke dalam direktori sebelum mengeksekusi renameperintah, tidak seperti -exec.

-depth memastikan bahwa penggantian nama terjadi pertama kali pada anak-anak, dan kemudian pada orang tua, untuk mencegah kemungkinan masalah dengan direktori orang tua yang hilang.

-execdir diperlukan karena penggantian nama tidak berfungsi dengan baik dengan jalur input non-basename, misalnya yang gagal berikut:

rename 's/findme/replaceme/g' acc/acc

The PATHhacking diperlukan karena -execdirmemiliki satu kelemahan yang sangat mengganggu: findyang sangat dogmatis dan menolak untuk melakukan apa saja dengan -execdirjika Anda memiliki path relatif di Anda PATHvariabel lingkungan, misalnya ./node_modules/.bin, gagal dengan:

find: Path relatif './node_modules/.bin' termasuk dalam variabel lingkungan PATH, yang tidak aman dalam kombinasi dengan aksi -execdir dari find. Harap hapus entri itu dari $ PATH

Lihat juga: Mengapa menggunakan tindakan '-execdir' tidak aman untuk direktori yang ada di PATH?

-execdiradalah ekstensi menemukan GNU ke POSIX . renameberbasis Perl dan berasal dari renamepaket. Diuji di Ubuntu 18.10.

Ganti nama lookahead workaround

Jika jalur input Anda tidak berasal find, atau jika Anda sudah cukup mengalami gangguan jalur relatif, kita dapat menggunakan beberapa Perl lookahead untuk mengganti nama direktori dengan aman seperti di:

git ls-files | sort -r | xargs rename 's/findme(?!.*\/)\/?$/replaceme/g' '{}'

Saya belum menemukan analog yang cocok untuk -execdirdengan xargs: https://superuser.com/questions/893890/xargs-change-working-directory-to-file-path-before-executing/915686

The sort -rdiperlukan untuk memastikan bahwa file datang setelah direktori masing-masing, karena jalur lagi datang setelah yang lebih pendek dengan awalan yang sama.

Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
sumber