Mengganti nama file gambar dalam jumlah besar dengan bash

16

Saya perlu mengganti nama kira-kira. 70.000 file. Misalnya: Dari sb_606_HBO_DPM_0089000ke sb_606_dpm_0089000dll.

Kisaran nomor berubah dari 0089000menjadi 0163022. Itu hanya bagian pertama dari nama yang perlu diubah. semua file berada dalam satu direktori, dan diberi nomor urut (urutan gambar). Angka-angka harus tetap tidak berubah.

Ketika saya mencoba ini di bash grizzles pada saya bahwa 'Daftar argumen terlalu panjang'.

Edit:

Saya pertama kali mencoba mengubah nama satu file dengan mv:

mv sb_606_HBO_DPM_0089000.dpx sb_606_dpm_0089000.dpx

Kemudian saya mencoba mengubah nama suatu range (saya belajar di sini minggu lalu bagaimana memindahkan banyak file, jadi saya pikir sintaks yang sama mungkin bekerja untuk mengubah nama file ...). Saya pikir saya sudah mencoba yang berikut (atau yang sejenisnya):

mv sb_606_HBO_DPM_0{089000..163023}.dpx sb_606_dpm_0{089000..163023}.dpx
kaya
sumber
4
Bagi pengulas : Saya rasa ini bukan duplikat; sebagian besar jawaban CLI pada pertanyaan lain tidak akan berfungsi di sini karena sejumlah besar file bertabrakan dengan batas shell ARG_MAX. Karena pertanyaan ini secara eksplisit meminta solusi baris perintah, solusi GUI (mungkin sama) seperti pada pertanyaan lain juga tidak cocok.
hidangan penutup
1
Saya tidak berpikir ini adalah penipuan karena boleh memiliki lebih dari satu pertanyaan tentang mengganti nama file. Tolong jangan tutup pertanyaan spesifik terhadap sumber daya umum yang tidak benar-benar menjawabnya ...
Zanna
1
@ Kaya Jika Anda dapat mengedit secara eksplisit perintah apa yang Anda coba, maka akan lebih jelas bahwa ini bukan penipuan. (Ini menunjukkan kepada kami bahwa Anda menyadari pendekatan ini.) Cheers.
Sparhawk
2
kaya, pertanyaan Anda bukan penipuan, karena pertanyaan khusus. Jangan khawatir tentang itu. Lebih penting lagi, setelah sebuah pertanyaan menerima sejumlah jawaban yang di-upgrade, mengeditnya mungkin bukan ide yang baik karena hasil edit Anda mungkin membuat jawaban yang ada kurang valid. Sekarang saya merasa jawaban saya harus menjelaskan mengapa mv {1..2} {3..4}tidak bekerja, yang merupakan masalah yang sama sekali berbeda dari ARG_MAX... Semua orang yang menjawab mungkin akan merasakan hal yang sama! Jadi, dari sudut pandang saya, saya berharap Anda akan mengembalikan suntingan terakhir Anda dan, jika Anda mau, tanyakan pertanyaan baru tentang mving dengan rentang
Zanna
1
@Sparhawk OP menulis dengan sangat jelas, dari versi pertama pertanyaan, bahwa masalahnya adalah argument list too longkesalahan. Tidak perlu mengklarifikasi lebih lanjut, ini jelas bukan penipuan karena kami membutuhkan solusi untuk berurusan dengan ARG_MAX dan jawaban dalam duplikat yang diusulkan tidak melakukan itu.
terdon

Jawaban:

25

Salah satu cara adalah dengan menggunakan finddengan -exec, dan +pilihan. Ini membangun daftar argumen, tetapi memecah daftar menjadi panggilan sebanyak yang diperlukan untuk beroperasi pada semua file tanpa melebihi daftar argumen maksimum. Sangat cocok ketika semua argumen akan diperlakukan sama. Inilah yang terjadi dengan rename, meskipun tidak dengan mv.

Anda mungkin perlu menginstal nama Perl:

sudo apt install rename

Maka Anda dapat menggunakan, misalnya:

find . -maxdepth 1 -exec rename -n 's/_HBO_DPM_/_dpm_/' {} +

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

Zanna
sumber
11

Saya akan menyarankan tiga alternatif. Masing-masing adalah perintah baris tunggal yang sederhana, tetapi saya akan memberikan varian untuk kasus yang lebih rumit, terutama jika file yang akan diproses dicampur dengan file lain dalam direktori yang sama.

mmv

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

mmv '*HBO_DPM*' '#1dpm#2'

Perhatikan bahwa argumen dilewatkan sebagai string, sehingga ekspansi glob tidak terjadi di shell. Perintah menerima tepat dua argumen, dan kemudian menemukan file yang sesuai secara internal, tanpa batasan ketat pada jumlah file. Perhatikan juga bahwa perintah di atas mengasumsikan bahwa semua file yang cocok dengan gumpalan pertama harus diganti namanya. Tentu saja Anda bebas untuk lebih spesifik:

mmv 'sb_606_HBO_DPM_*' 'sb_606_dpm_#1'

Jika Anda memiliki file di luar rentang angka yang diminta dalam direktori yang sama, Anda mungkin lebih baik dengan perulangan nomor yang diberikan lebih lanjut dalam jawaban ini. Namun Anda juga bisa menggunakan urutan doa mmv dengan pola yang sesuai:

mmv 'sb_606_HBO_DPM_0089*'       'sb_606_dpm_0089#1'    # 0089000-0089999
mmv 'sb_606_HBO_DPM_009*'        'sb_606_dpm_009#1'     # 0090000-0099999
mmv 'sb_606_HBO_DPM_01[0-5]*'    'sb_606_dpm_01#1#2'    # 0100000-0159999
mmv 'sb_606_HBO_DPM_016[0-2]*'   'sb_606_dpm_016#1#2'   # 0160000-0162999
mmv 'sb_606_HBO_DPM_01630[01]?'  'sb_606_dpm_01630#1#2' # 0163000-0163019
mmv 'sb_606_HBO_DPM_016302[0-2]' 'sb_606_dpm_016302#1'  # 0163020-0163022

putaran angka

Jika Anda ingin menghindari memasang apa pun, atau perlu memilih dengan rentang nomor menghindari kecocokan di luar kisaran ini, dan Anda siap untuk menunggu 74.023 permintaan perintah, Anda bisa menggunakan bash loop polos:

for i in {0089000..0163022}; do mv sb_606_HBO_DPM_$i sb_606_dpm_$i; done

Ini bekerja sangat baik di sini karena tidak ada celah dalam urutan. Kalau tidak, Anda mungkin ingin memeriksa apakah file sumber benar-benar ada.

for i in {0089000..0163022}; do
  test -e sb_606_HBO_DPM_$i && mv sb_606_HBO_DPM_$i sb_606_dpm_$i
done

Perhatikan bahwa berbeda dengan for ((i=89000; i<=163022; ++i))ekspansi brace yang menangani angka nol terkemuka sejak beberapa Bash merilis beberapa tahun yang lalu. Sebenarnya perubahan yang saya minta, jadi saya senang melihat kasus penggunaan untuk itu.

Bacaan lebih lanjut: Perluasan Brace di halaman info Bash, khususnya bagian tentang {x..y[..incr]}.

loop file

Pilihan lain adalah melompati glob yang sesuai, alih-alih melompati rentang integer yang dimaksud. Sesuatu seperti ini:

for i in *HBO_DPM*; do mv "$i" "${i/HBO_DPM/dpm}"; done

Sekali lagi ini adalah satu mvpermintaan per file. Dan lagi loop adalah daftar elemen yang panjang, tetapi seluruh daftar tidak diteruskan sebagai argumen untuk suatu subproses, tetapi ditangani secara internal oleh bash, sehingga batas tidak akan menyebabkan masalah.

Bacaan lebih lanjut: Ekspansi Parameter Shell di halaman info Bash, mendokumentasikan ${parameter/pattern/string}antara lain.

Jika Anda ingin membatasi rentang angka ke yang Anda berikan, Anda dapat menambahkan tanda centang untuk itu:

for i in sb_606_HBO_DPM_+([0-9]); do
  if [[ "${i##*_*(0)}" -ge 89000 ]] && [[ "${i##*_*(0)}" -le 163022 ]]; then
    mv "$i" "${i/HBO_DPM/dpm}"
  fi
done

Di sini ${i##pattern}menghapus pencocokan awalan terpanjang patterndari $i. Awalan terpanjang itu didefinisikan sebagai apa saja, lalu garis bawah, lalu nol atau lebih nol. Yang terakhir ditulis sebagai *(0)yang merupakan pola gumpal diperpanjang yang tergantung pada extglobopsi yang ditetapkan. Menghapus angka nol di depan adalah penting untuk memperlakukan angka sebagai basis 10 bukan basis 8. +([0-9])Argumen loop adalah glob yang diperluas, cocok dengan satu atau lebih digit, kalau-kalau Anda memiliki file di sana yang memulai yang sama tetapi tidak berakhir dengan jumlah.

MvG
sumber
Terima kasih! Ini bekerja seperti mimpi: untuk saya di {0089000..0163022}; lakukan mv sb_606_HBO_DPM_ $ i sb_606_dpm_ $ i; selesai - saya harus menambahkan ekstensi nama file untuk membuatnya berfungsi, tetapi itu hanya melakukan apa yang saya inginkan dan saya bahkan mengerti sintaks. Terima kasih @MGG
kaya
@ Rich: Senang saya bisa membantu - Anda dan semoga pengunjung masa depan juga. Jangan lupa untuk menerima jawaban yang paling berguna. Anda selalu dapat mengubah tanda centang itu di masa mendatang jika ada sesuatu yang lebih baik datang.
MvG
10

Salah satu cara untuk mengatasi ARG_MAXbatas adalah dengan menggunakan built-in bash shell printf:

printf '%s\0' sb_* | xargs -0 rename -n 's/HBO_DPM/dpm/'

Ex.

rename -n 's/HBO_DPM/dpm/' sb_*
bash: /usr/bin/rename: Argument list too long

tapi

printf '%s\0' sb_* | xargs -0 rename -n 's/HBO_DPM/dpm/'
rename(sb_606_HBO_DPM_0089000, sb_606_dpm_0089000)
.
.
.
rename(sb_606_HBO_DPM_0163022, sb_606_dpm_0163022)
Steeldriver
sumber
7
find . -type f -exec bash -c 'echo $1 ${1/HBO_DPM/dpm}' _ {} \;
./sb_606_HBO_DPM_0089000 ./sb_606_dpm_0089000

finddalam direktori saat ini .untuk semua file -type fdan lakukan rename file yang ditemukan $1dengan mengganti HBO_DPMdengan dmp satu per satu-exec ... \;

ganti echodengan mvuntuk melakukan rename.

αғsнιη
sumber
6

Anda bisa menulis skrip python kecil, seperti:

import os
for file in os.listdir("."):
    os.rename(file, file.replace("HBO_DPM", "dpm"))

Simpan itu sebagai file teks seperti rename.pydalam folder file, lalu dengan terminal di folder itu pergi:

python rename.py
Tengkorak Batu
sumber
6

Anda dapat melakukannya file per file (mungkin butuh waktu) dengan

sudo apt install util-linux  # if you don't have it already
for i in *; do rename.ul HBO_DPM dpm "$i"; done

Seperti Perl yang renamedigunakan dalam jawaban lain, rename.uljuga memiliki opsi -natau --no-actuntuk pengujian.

muclux
sumber
Saya telah mengedit komentar Anda tentang jawaban Zanna, harap edit jawaban Zanna atau tinggalkan komentar.
fosslinux
@ubashu yang bukan komentar pada jawaban saya - itu merujuk pada -nbendera yang saya gunakan untuk pengujian dan menyarankan itu dapat digunakan rename.uljuga.
Zanna
3

Saya melihat bahwa tidak ada yang mengundang teman baik saya sedke pesta :). forLoop berikut akan mencapai tujuan Anda:

for i in sb_606_HBO_DPM*; do
  mv "$i" "$(echo $i | sed 's/HBO_DPM/dpm/')";
done

Ada banyak alat untuk pekerjaan seperti itu, pilih salah satu yang paling dimengerti untuk Anda. Yang ini sederhana dan mudah diubah sesuai dengan ini atau tujuan lain ...

andrew.46
sumber
Memang, tidak terlalu relevan dalam kasus khusus ini, tetapi ini akan gagal jika ada nama file yang mengandung baris baru. Saya menyebutkan ini karena sebagian besar (semua?) Jawaban lain kuat dan dapat menangani nama file yang sewenang-wenang, atau hanya bekerja pada skema penamaan file OP.
terdon
... baris baru, spasi, wildcard, ... beberapa di antaranya dapat dihindari dengan mengutip $idalam substitusi perintah, tetapi tidak ada cara mudah untuk menangani baris baru yang tertinggal dalam nama file.
muru
3

Karena kita memberikan opsi, inilah pendekatan Perl. cdke direktori target dan jalankan:

perl -e 'foreach(glob("sb_*")){rename $_, s/_HBO_DPM_/_dpm_/r}'

Penjelasan

  • perl -e: jalankan skrip yang diberikan oleh -e.
  • foreach(glob){}: jalankan apa pun yang ada di { }pada setiap hasil dari glob.
  • glob("sb_*"): mengembalikan daftar semua file dan direktori di direktori saat ini yang namanya cocok dengan shell glob sb*.
  • rename $_, s/_HBO_DPM_/_dpm_/r: perl magic. $_adalah variabel khusus yang menampung setiap elemen yang kita iterasi (dalam foreach). Jadi di sini, itu akan menjadi setiap file yang ditemukan. s/_HBO_DPM_/_dpm_/menggantikan kemunculan pertama _HBO_DPM_dengan _dpm_. Ini berjalan $_secara default, sehingga akan berjalan di setiap nama file. The /rberarti "berlaku pengganti ini untuk salinan string sasaran (nama file) dan mengembalikan string dimodifikasi. renameMelakukan apa yang Anda harapkan: itu mengganti nama file Jadi semuanya akan mengubah nama nama file saat ini (. $_) Untuk dirinya sendiri dengan _HBO_DPM_diganti oleh _dpm_.

Anda dapat menulis hal yang sama dengan skrip yang diperluas (dan skrip yang lebih mudah dibaca):

#! /usr/bin/env perl
use strict;
use warnings;

foreach my $fileName (glob("sb_*")){
  ## Copy the name to a new variable
  my $newName = $fileName;
  ## change the copy. $newName is now the changed version
  $newName =~ s/_HBO_DPM_/_dpm_/;
  ## rename
  rename $fileName, $newName;
}
terdon
sumber
1

Bergantung pada jenis penggantian nama yang Anda impikan, menggunakan vidir dengan pengeditan beberapa baris mungkin memuaskan.
Dalam kasus khusus Anda, Anda dapat memilih semua baris dalam editor teks Anda dan menghapus bagian _ " HBO" dari nama file dalam beberapa penekanan tombol.

kraymer
sumber
ya, vi telah menemukan dan mengganti gloable.
Jasen
2
Bisakah Anda memperluas jawaban Anda dan memberi contoh bagaimana mencapai tujuan OP vidir?
hidangan penutup