Menggunakan sed untuk mengganti nama file secara massal

89

Objektif

Ubah nama file ini:

  • F00001-0708-RG-biasliuyda
  • F00001-0708-CS-akgdlaul
  • F00001-0708-VF-hioulgigl

ke nama file ini:

  • F0001-0708-RG-biasliuyda
  • F0001-0708-CS-akgdlaul
  • F0001-0708-VF-hioulgigl

Kode Shell

Untuk mengetes:

ls F00001-0708-*|sed 's/\(.\).\(.*\)/mv & \1\2/'

Untuk melakukan:

ls F00001-0708-*|sed 's/\(.\).\(.*\)/mv & \1\2/' | sh

Pertanyaan saya

Saya tidak mengerti kode sed. Saya mengerti apa perintah substitusi

$ sed 's/something/mv'

cara. Dan saya agak memahami ekspresi reguler. Tapi saya tidak mengerti apa yang terjadi di sini:

\(.\).\(.*\)

atau di sini:

& \1\2/

Yang pertama, bagi saya, sepertinya itu berarti: "satu karakter, diikuti oleh satu karakter, diikuti dengan urutan panjang apa pun dari satu karakter" - tapi pasti ada yang lebih dari itu. Sejauh bagian terakhir:

& \1\2/

Saya tidak punya ide.

Daniel Underwood
sumber

Jawaban:

152

Pertama, saya harus mengatakan bahwa cara termudah untuk melakukan ini adalah dengan menggunakan perintah prename atau ganti nama.

Di Ubuntu, OSX (paket Homebrew rename, paket MacPorts p5-file-rename), atau sistem lain dengan perl rename (nama awal):

rename s/0000/000/ F0000*

atau pada sistem dengan ganti nama dari util-linux-ng, seperti RHEL:

rename 0000 000 F0000*

Itu jauh lebih bisa dimengerti daripada perintah sed yang setara.

Tetapi untuk memahami perintah sed, halaman manual sed sangat membantu. Jika Anda menjalankan man sed dan mencari & (menggunakan / perintah untuk mencari), Anda akan menemukan itu adalah karakter khusus di s / foo / bar / replacements.

  s/regexp/replacement/
         Attempt  to match regexp against the pattern space.  If success‐
         ful,  replace  that  portion  matched  with  replacement.    The
         replacement may contain the special character & to refer to that
         portion of the pattern space  which  matched,  and  the  special
         escapes  \1  through  \9  to refer to the corresponding matching
         sub-expressions in the regexp.

Oleh karena itu, \(.\)cocokkan karakter pertama, yang dapat dirujuk oleh \1. Kemudian .cocok dengan karakter berikutnya, yang selalu 0. Kemudian \(.*\)cocok dengan sisa nama file, yang dapat direferensikan oleh \2.

String pengganti menyatukan semuanya menggunakan &(nama file asli) dan \1\2yang merupakan setiap bagian dari nama file kecuali karakter ke-2, yang merupakan 0.

Ini adalah cara yang cukup samar untuk melakukan ini, IMHO. Jika karena alasan tertentu perintah ganti nama tidak tersedia dan Anda ingin menggunakan sed untuk mengganti nama (atau mungkin Anda melakukan sesuatu yang terlalu rumit untuk diganti nama?), Menjadi lebih eksplisit dalam regex Anda akan membuatnya lebih mudah dibaca. Mungkin sesuatu seperti:

ls F00001-0708-*|sed 's/F0000\(.*\)/mv & F000\1/' | sh

Mampu melihat apa yang sebenarnya berubah di s / search / replacement / membuatnya lebih mudah dibaca. Juga tidak akan terus menyedot karakter dari nama file Anda jika Anda tidak sengaja menjalankannya dua kali atau sesuatu.

Edward Anderson
sumber
1
di server RHEL saya, sintaks ganti nama akan menjadi "ganti nama 0000 000 F0000 *"
David LeBauer
1
Kemungkinan besar itu renamesendiri adalah tautan yang "diubah namanya" . ie renametelah "diganti namanya" dari prename.. misalnya, di Ubuntu: readlink -f $(which rename)output /usr/bin/prename... Yang renamedisebutkan oleh David adalah program yang sama sekali berbeda.
Peter.O
1
Poin yang bagus, Peter. Saya telah memperbarui jawaban untuk mengatasi kedua ganti nama utilitas.
Edward Anderson
3
Untuk men-debug ini, hapus pipa ke sh di bagian akhir. Perintah akan bergema ke layar.
Ben Mathews
1
Apakah Anda yakin memberikan saran yang baik untuk menyalurkan data acak sh? ini berpotensi berbahaya karena kode arbitrer dapat dieksekusi (Anda memperlakukan data sebagai kode).
gniourf_gniourf
46

Anda sudah memiliki penjelasan sed, sekarang Anda dapat menggunakan shell saja, tidak perlu perintah eksternal

for file in F0000*
do
    echo mv "$file" "${file/#F0000/F000}"
    # ${file/#F0000/F000} means replace the pattern that starts at beginning of string
done
anjing hantu74
sumber
1
Bagus, tetapi Anda tidak dapat membuat referensi dengan tanda kurung.
Leonidas Tsampros
28

Saya menulis posting kecil dengan contoh tentang penggantian nama batch menggunakan sedbeberapa tahun yang lalu:

http://www.guyrutenberg.com/2009/01/12/batch-renaming-using-sed/

Sebagai contoh:

for i in *; do
  mv "$i" "`echo $i | sed "s/regex/replace_text/"`";
done

Jika regex berisi grup (mis. \(subregex\) Maka Anda dapat menggunakannya dalam teks pengganti sebagai \1\, \2dll.

Orang
sumber
Perhatikan bahwa jawaban hanya tautan tidak disarankan (tautan cenderung menjadi usang seiring waktu). Harap pertimbangkan untuk mengedit jawaban Anda dan menambahkan sinopsis di sini.
kleopatra
tidak seefisien itu, tetapi menyelesaikan pekerjaan untuk beberapa ratus file. Suara positif.
Varun Chandak
23

Cara termudah adalah:

for i in F00001*; do mv "$i" "${i/F00001/F0001}"; done

atau, secara portabel,

for i in F00001*; do mv "$i" "F0001${i#F00001}"; done

Ini menggantikan F00001awalan di nama file dengan F0001. kredit untuk mahesh di sini: http://www.debian-administration.org/articles/150

Mike
sumber
3
Anda harus mengutip dengan benar interpolasi variabel; mv "$i" "${i/F00001/F0001}". Tapi +1
tripleee pada
7

The sedperintah

s/\(.\).\(.*\)/mv & \1\2/

sarana untuk menggantikan:

\(.\).\(.*\)

dengan:

mv & \1\2

seperti sedperintah biasa . Namun, tanda kurung &dan \npenanda mengubahnya sedikit.

String pencarian cocok (dan mengingat sebagai pola 1) karakter tunggal di awal, diikuti oleh satu karakter, diikuti oleh sisa string (diingat sebagai pola 2).

Dalam string pengganti, Anda dapat merujuk ke pola yang cocok ini untuk menggunakannya sebagai bagian dari pengganti. Anda juga bisa merujuk ke seluruh bagian yang cocok sebagai &.

Jadi apa yang dilakukan sedperintah itu adalah membuat mvperintah berdasarkan file asli (untuk sumber) dan karakter 1 dan 3 dan seterusnya, secara efektif menghapus karakter 2 (untuk tujuan). Ini akan memberi Anda serangkaian garis di sepanjang format berikut:

mv F00001-0708-RG-biasliuyda F0001-0708-RG-biasliuyda
mv abcdef acdef

dan seterusnya.

paxdiablo
sumber
1
Ini adalah penjelasan yang bagus, tetapi akan berguna untuk menunjukkan bagaimana Anda menggunakan perintah sed dengan perintah lain untuk benar-benar mengganti nama file. Misalnya:ls | sed "s/\(.\).\(.*\)/mv & \1\2/" | bash
jcarballo
@jcarballo: berbahaya untuk mengurai ls, menyalurkan, seddan kemudian menyalurkan melalui cangkang! itu tunduk pada eksekusi kode arbitrer dengan nama file palsu. Masalahnya adalah bahwa data harus diperlakukan sebagai data, dan di sini biasanya diserialkan menjadi kode tanpa tindakan pencegahan apa pun. Saya berharap paxdiablo dapat menghapus jawaban ini karena ini benar-benar tidak menunjukkan praktik yang baik. (Saya tersandung pada pertanyaan ini karena seorang pemula secara acak menyalurkan | shperintah yang tidak berhasil dan setelah melihat pertanyaan ini dan jawabannya mengira itu akan bekerja lebih baik — saya ngeri!) :).
gniourf_gniourf
3

Kata backslash-paren berarti, "sambil mencocokkan pola, pegang benda yang cocok di sini." Nanti, di sisi teks pengganti, Anda bisa mendapatkan kembali fragmen yang diingat dengan "\ 1" (blok tanda kurung pertama), "\ 2" (blok kedua), dan seterusnya.

Runcing
sumber
1

Jika semua yang Anda lakukan sebenarnya adalah menghapus karakter kedua, apa pun itu, Anda dapat melakukan ini:

s/.//2

tetapi perintah Anda adalah membuat mvperintah dan mengirimkannya ke shell untuk dieksekusi.

Ini tidak lebih dapat dibaca dari versi Anda:

find -type f | sed -n 'h;s/.//4;x;s/^/mv /;G;s/\n/ /g;p' | sh

Karakter keempat dihapus karena findmempersiapkan setiap nama file dengan "./".

Dennis Williamson
sumber
Saya berharap Anda dapat menghapus jawaban ini. Meskipun mungkin bagus dalam kasus OP yang sangat spesifik, ada banyak orang yang melihat jawaban seperti ini dan tidak memahaminya, dan secara acak menyalurkan | shperintah yang tidak berfungsi, dengan harapan itu akan berhasil lebih baik. Mengerikan! (dan selain itu, itu bukan praktik yang baik). Saya harap Anda mengerti!
gniourf_gniourf
1

Menggunakan perl rename ( harus ada di toolbox):

rename -n 's/0000/000/' F0000*

Lepaskan -nsakelar ketika output terlihat bagus untuk mengganti nama secara nyata.

peringatan Ada alat lain dengan nama yang sama yang mungkin dapat atau tidak dapat melakukan ini, jadi berhati-hatilah.

Perintah ganti nama yang merupakan bagian dari util-linuxpaket, tidak akan.

Jika Anda menjalankan perintah berikut ( GNU)

$ rename

dan Anda lihat perlexpr, ini sepertinya alat yang tepat.

Jika belum, jadikan default (biasanya sudah case) on Debiandan turunannya seperti Ubuntu:

$ sudo apt install rename
$ sudo update-alternatives --set rename /usr/bin/file-rename

Untuk archlinux:

pacman -S perl-rename

Untuk distro keluarga RedHat:

yum install prename

Paket 'prename' ada di repositori EPEL .


Untuk Gentoo:

emerge dev-perl/rename

Untuk * BSD:

pkg install gprename

atau p5-File-Rename


Untuk pengguna Mac:

brew install rename

Jika Anda tidak memiliki perintah ini dengan distro lain, cari manajer paket Anda untuk menginstalnya atau lakukan secara manual :

cpan -i File::Rename

Versi mandiri lama dapat ditemukan di sini


ganti nama pria


Alat ini aslinya ditulis oleh Larry Wall, ayah Perl.

Gilles Quenot
sumber
0

Tanda kurung menangkap string tertentu untuk digunakan oleh angka yang miring terbalik.

Ewan Todd
sumber
0
 ls F00001-0708-*|sed 's|^F0000\(.*\)|mv & F000\1|' | bash
anjing hantu74
sumber
Mengerikan! tunduk pada eksekusi kode arbitrer (mungkin tidak dalam konteks spesifik pertanyaan, tetapi ada banyak orang yang melihat jawaban seperti ini dan mencoba mengetik secara acak sesuatu yang terlihat seperti itu, dan berbahaya menakutkan!). Saya berharap Anda dapat menghapus jawaban ini (selain itu, Anda memiliki jawaban bagus lainnya di sini, yang saya beri suara positif).
gniourf_gniourf
0

Inilah yang akan saya lakukan:

for file in *.[Jj][Pp][Gg] ;do 
    echo mv -vi \"$file\" `jhead $file|
                           grep Date|
                           cut -b 16-|
                           sed -e 's/:/-/g' -e 's/ /_/g' -e 's/$/.jpg/g'` ;
done

Kemudian jika itu terlihat oke, tambahkan | shke bagian akhir. Begitu:

for file in *.[Jj][Pp][Gg] ;do 
    echo mv -vi \"$file\" `jhead $file|
                           grep Date|
                           cut -b 16-|
                           sed -e 's/:/-/g' -e 's/ /_/g' -e 's/$/.jpg/g'` ;
done | sh
Chris Po
sumber
0

Beberapa contoh yang berhasil untuk saya:

$ tree -L 1 -F .
.
├── A.Show.2020.1400MB.txt
└── Some Show S01E01 the Loreming.txt

0 directories, 2 files

## remove "1400MB" (I: ignore case) ...

$ for f in *; do mv 2>/dev/null -v "$f" "`echo $f | sed -r 's/.[0-9]{1,}mb//I'`"; done;
renamed 'A.Show.2020.1400MB.txt' -> 'A.Show.2020.txt'

## change "S01E01 the" to "S01E01 The"
## \U& : change (here: regex-selected) text to uppercase;
##       note also: no need here for `\1` in that regex expression

$ for f in *; do mv 2>/dev/null "$f" "`echo $f | sed -r "s/([0-9] [a-z])/\U&/"`"; done

$ tree -L 1 -F .
.
├── A.Show.2020.txt
└── Some Show S01E01 The Loreming.txt

0 directories, 2 files
$ 
Victoria Stuart
sumber
-1
for i in *; do mv $i $(echo $i|sed 's/AAA/BBB/'); done
pengguna3164360
sumber
4
Selamat datang di SO. Harap pertimbangkan untuk menambahkan penjelasan kode Anda. Ini akan membantu pengguna lain dalam memahaminya.
Digvijay S
Jawaban ini bagus, tetapi ini adalah jawaban yang hampir duplikat dari jawaban yang mendapat suara tinggi di atas.
Eric Leschinski