Batch mengganti nama file dengan Bash

101

Bagaimana Bash bisa mengganti nama serangkaian paket untuk menghapus nomor versinya? Saya telah bermain-main dengan keduanya exprdan %%, tidak berhasil.

Contoh:

Xft2-2.1.13.pkg menjadi Xft2.pkg

jasper-1.900.1.pkg menjadi jasper.pkg

xorg-libXrandr-1.2.3.pkg menjadi xorg-libXrandr.pkg

Jeremy L.
sumber
1
Saya bermaksud untuk menggunakan ini secara teratur, sebagai skrip tulis-sekali pakai-banyak. Sistem apa pun yang akan saya gunakan akan memiliki bash, jadi saya tidak takut dengan bashisme yang cukup berguna.
Jeremy L
Lebih umum lihat juga stackoverflow.com/questions/28725333/…
tripleee

Jawaban:

190

Anda dapat menggunakan fitur perluasan parameter bash

for i in ./*.pkg ; do mv "$i" "${i/-[0-9.]*.pkg/.pkg}" ; done

Kutipan diperlukan untuk nama file dengan spasi.

richq
sumber
1
Saya juga menyukainya, tetapi menggunakan fitur khusus bash ... Saya biasanya tidak menggunakannya untuk mendukung sh "standar".
Diego Sevilla
2
Untuk hal-hal interaktif satu baris sekali pakai, saya selalu menggunakan ini daripada sed-fu. Jika itu sebuah naskah, ya, hindari bashisme.
richq
Satu masalah yang saya temukan dengan kode: Apa yang terjadi jika file sudah diganti namanya dan saya menjalankannya lagi? Saya mendapat peringatan karena mencoba pindah ke subdirektori itu sendiri.
Jeremy L
1
Untuk menghindari error re-run, Anda dapat menggunakan pola yang sama di bagian " .pkg", yaitu "untuk i di * - [0-9.] .Pkg; do mv $ i $ {i / - [0-9 .] *. pkg / .pkg}; selesai ". Tetapi kesalahannya cukup tidak berbahaya (pindah ke file yang sama).
richq
3
Bukan solusi bash, tetapi jika Anda telah menginstal perl, itu menyediakan perintah ganti nama yang berguna untuk penggantian nama batch, cukup lakukanrename "s/-[0-9.]*//" *.pkg
Rufus
33

Jika semua file berada di direktori yang sama, urutannya

ls | 
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' | 
sh

akan melakukan pekerjaanmu. The sed perintah akan membuat urutan dari mv perintah, yang kemudian dapat pipa ke shell. Sebaiknya jalankan pipeline terlebih dahulu tanpa trailing | shuntuk memverifikasi bahwa perintah tersebut melakukan apa yang Anda inginkan.

Untuk mengulang melalui beberapa direktori, gunakan sesuatu seperti

find . -type f |
sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' |
sh

Perhatikan bahwa dalam sed , urutan pengelompokan ekspresi reguler adalah tanda kurung yang diawali dengan garis miring terbalik, \(dan \), bukan tanda kurung tunggal (dan ).

Diomidis Spinellis
sumber
Maafkan kenaifan saya, tetapi bukankah keluar dari tanda kurung dengan garis miring terbalik membuatnya diperlakukan sebagai karakter literal?
devios1
2
Sed urutan pengelompokan ekspresi reguler adalah (, bukan braket tunggal.
Diomidis Spinellis
tidak berhasil untuk saya, akan senang mendapatkan bantuan Anda .... Akhiran file FROM ditambahkan ke file TO: $ find. -tipe d | sed -n 's /(.*)\/(.*) anysoftkeyboard (. *) / mv "\ 1 \ / \ 2anysoftkeyboard \ 3" "\ 1 \ / \ 2effectedkeyboard \ 3" / p' | sh >> >>>>> OUTPUT >>>> mv: ganti nama ./base/src/main/java/com/anysoftkeyboard/base/dictionaries menjadi ./base/src/main/java/com/effectedkeyboard/base/dictionaries/dictionaries : Tidak ada file atau direktori seperti itu
Vitali Pom
Anda tampaknya kehilangan garis miring terbalik di tanda kurung.
Diomidis Spinellis
1
Jangan gunakan lsdalam skrip. Variasi pertama dapat dicapai dengan printf '%s\n' * | sed ...dan dengan beberapa kreativitas Anda mungkin dapat menyingkirkannya sedjuga. Bash's printfmenawarkan '%q'kode format yang bisa berguna jika Anda memiliki nama file yang berisi metakarakter shell literal.
tripleee
10

Saya akan melakukan sesuatu seperti ini:

for file in *.pkg ; do
    mv $file $(echo $file | rev | cut -f2- -d- | rev).pkg
done

seharusnya semua file Anda ada di direktori saat ini. Jika tidak, coba gunakan temukan seperti yang disarankan di atas oleh Javier.

EDIT : Juga, versi ini tidak menggunakan fitur khusus bash, seperti yang lain di atas, yang membuat Anda lebih mudah dibawa.

Diego Sevilla
sumber
Semuanya ada di direktori yang sama. Apa pendapat Anda tentang jawaban rq?
Jeremy L
Saya suka tidak menggunakan fitur khusus bash, tetapi sh yang lebih standar.
Diego Sevilla
1
Saya berasumsi ini untuk mengganti nama dengan cepat, bukan untuk menulis lintas platform generasi berikutnya, Aplikasi Perusahaan portabel ;-)
richq
1
Haha, cukup adil ... Hanya dari orang yang berpikiran portabilitas seperti saya :)
Diego Sevilla
Yang ini tidak memperhitungkan contoh ketiga: xorg-libXrandr (tanda hubung digunakan dalam nama).
Jeremy L
5

Berikut adalah POSIX yang hampir setara dengan jawaban yang diterima saat ini . Ini memperdagangkan ${variable/substring/replacement}perluasan parameter khusus Bash dengan yang tersedia di shell yang kompatibel dengan Bourne.

for i in ./*.pkg; do
    mv "$i" "${i%-[0-9.]*.pkg}.pkg"
done

Perluasan parameter ${variable%pattern}menghasilkan nilai variabledengan sufiks apa pun yang cocok patterndihapus. (Ada juga ${variable#pattern}untuk menghapus awalan.)

Saya menyimpan subpola -[0-9.]*dari jawaban yang diterima meskipun mungkin menyesatkan. Ini bukan ekspresi reguler, tapi pola glob; jadi ini tidak berarti "tanda hubung yang diikuti oleh nol atau lebih angka atau titik". Sebaliknya, itu berarti "tanda hubung, diikuti dengan angka atau titik, diikuti oleh apa saja". "Apa saja" akan menjadi pertandingan yang sesingkat mungkin, bukan yang terpanjang. (Bash menawarkan ##dan %%untuk memotong awalan atau sufiks terpanjang, bukan yang terpendek.)

tripleee
sumber
5

Kami dapat berasumsi sedtersedia di * nix apa pun, tetapi kami tidak dapat memastikan itu akan mendukung sed -nuntuk menghasilkan perintah mv. ( CATATAN: Hanya GNU yang sedmelakukan ini.)

Meski begitu, bash builtins dan sed, kita dapat dengan cepat menyiapkan fungsi shell untuk melakukan ini.

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      mv -v "$file" "$(sed $sed_pattern <<< $file)"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

Pemakaian

sedrename 's|\(.*\)\(-[0-9.]*\.pkg\)|\1\2|' *.pkg

before:

./Xft2-2.1.13.pkg
./jasper-1.900.1.pkg
./xorg-libXrandr-1.2.3.pkg

after:

./Xft2.pkg
./jasper.pkg
./xorg-libXrandr.pkg

Membuat folder target:

Karena mvtidak secara otomatis membuat folder target, kami tidak dapat menggunakan versi awal kamisedrename .

Ini adalah perubahan yang cukup kecil, jadi akan menyenangkan untuk menyertakan fitur itu:

Kita memerlukan fungsi utilitas, abspath(atau jalur absolut) karena bash tidak memiliki build ini.

abspath () { case "$1" in
               /*)printf "%s\n" "$1";;
               *)printf "%s\n" "$PWD/$1";;
             esac; }

Setelah kita memilikinya, kita dapat menghasilkan folder target untuk pola sed / rename yang menyertakan struktur folder baru.

Ini akan memastikan kami mengetahui nama folder target kami. Saat kami mengganti nama, kami harus menggunakannya pada nama file target.

# generate the rename target
target="$(sed $sed_pattern <<< $file)"

# Use absolute path of the rename target to make target folder structure
mkdir -p "$(dirname $(abspath $target))"

# finally move the file to the target name/folders
mv -v "$file" "$target"

Inilah skrip sadar folder lengkap ...

sedrename() {
  if [ $# -gt 1 ]; then
    sed_pattern=$1
    shift
    for file in $(ls $@); do
      target="$(sed $sed_pattern <<< $file)"
      mkdir -p "$(dirname $(abspath $target))"
      mv -v "$file" "$target"
    done
  else
    echo "usage: $0 sed_pattern files..."
  fi
}

Tentu saja, ini masih berfungsi ketika kami tidak memiliki folder target tertentu juga.

Jika kami ingin memasukkan semua lagu ke dalam folder, ./Beethoven/ kami dapat melakukan ini:

Pemakaian

sedrename 's|Beethoven - |Beethoven/|g' *.mp3

before:

./Beethoven - Fur Elise.mp3
./Beethoven - Moonlight Sonata.mp3
./Beethoven - Ode to Joy.mp3
./Beethoven - Rage Over the Lost Penny.mp3

after:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

Putaran bonus ...

Menggunakan skrip ini untuk memindahkan file dari folder ke dalam satu folder:

Dengan asumsi kami ingin mengumpulkan semua file yang cocok, dan menempatkannya di folder saat ini, kami dapat melakukannya:

sedrename 's|.*/||' **/*.mp3

before:

./Beethoven/Fur Elise.mp3
./Beethoven/Moonlight Sonata.mp3
./Beethoven/Ode to Joy.mp3
./Beethoven/Rage Over the Lost Penny.mp3

after:

./Beethoven/ # (now empty)
./Fur Elise.mp3
./Moonlight Sonata.mp3
./Ode to Joy.mp3
./Rage Over the Lost Penny.mp3

Perhatikan pola sed regex

Aturan pola sed reguler berlaku dalam skrip ini, pola ini bukan PCRE (Perl Compatible Regular Expressions). Anda bisa saja memperluas sintaks ekspresi reguler, menggunakan salah satu sed -ratau sed -E bergantung pada platform Anda.

Lihat kepatuhan POSIX man re_formatuntuk penjelasan lengkap tentang pola regexp dasar sed dan diperpanjang.

ocodo
sumber
4

Saya menemukan bahwa mengganti nama adalah alat yang jauh lebih mudah digunakan untuk hal semacam ini. Saya menemukannya di Homebrew untuk OSX

Sebagai contoh Anda, saya akan melakukan:

rename 's/\d*?\.\d*?\.\d*?//' *.pkg

'S' berarti pengganti. Bentuknya adalah s / searchPattern / replacement / files_to_apply. Anda perlu menggunakan regex untuk ini yang membutuhkan sedikit studi tetapi itu sepadan dengan usaha.

Simon Katan
sumber
Saya setuju. Untuk hal sesekali, menggunakan fordan sedlain - lain boleh-boleh saja, tetapi jauh lebih sederhana dan lebih cepat untuk digunakan renamejika Anda sering melakukannya.
argentum2f
Apakah Anda melarikan diri dari periode?
Uang Besar
Masalahnya adalah ini tidak portabel; tidak hanya tidak semua platform memiliki renameperintah; di samping itu, beberapa akan memiliki yang berbeda rename perintah dengan fitur yang berbeda, sintaks, dan / atau pilihan. Ini terlihat seperti Perl renameyang cukup populer; tetapi jawabannya harus benar-benar menjelaskan hal ini.
tripleee
3

lebih baik digunakan seduntuk ini, seperti:

find . -type f -name "*.pkg" |
 sed -e 's/((.*)-[0-9.]*\.pkg)/\1 \2.pkg/g' |
 while read nameA nameB; do
    mv $nameA $nameB;
 done

mencari ekspresi reguler dibiarkan sebagai latihan (seperti berurusan dengan nama file yang menyertakan spasi)

Javier
sumber
Terima kasih: Spasi tidak digunakan dalam nama.
Jeremy L
1

Ini tampaknya bekerja dengan asumsi itu

  • semuanya diakhiri dengan $ pkg
  • versi Anda # selalu dimulai dengan "-"

lepas .pkg, lalu lepas - ..

for x in $(ls); do echo $x $(echo $x | sed 's/\.pkg//g' | sed 's/-.*//g').pkg; done
Steve B.
sumber
Ada beberapa paket yang memiliki tanda hubung di namanya (lihat contoh ketiga). Apakah ini memengaruhi kode Anda?
Jeremy L.
1
Anda dapat menjalankan beberapa ekspresi sed menggunakan -e. Mis sed -e 's/\.pkg//g' -e 's/-.*//g'.
tacotuesday
Jangan mengurai lsoutput dan mengutip variabel Anda. Lihat juga shellcheck.net untuk mendiagnosis masalah umum dan antipattern dalam skrip shell.
tripleee
1

Saya memiliki beberapa *.txtfile untuk diubah namanya seperti .sqldi folder yang sama. di bawah ini berhasil untuk saya:

for i in \`ls *.txt | awk -F "." '{print $1}'\` ;do mv $i.txt $i.sql; done
Kumar Mashalkar
sumber
2
Anda dapat meningkatkan jawaban Anda dengan mengabstraksikannya ke kasus penggunaan OP yang sebenarnya.
dakab
Ini memiliki banyak masalah serta kesalahan sintaks yang jelas. Lihat shellcheck.net untuk memulai.
tripleee
0

Terima kasih atas jawaban ini. Saya juga punya masalah. Memindahkan file .nzb.queued ke file .nzb. Itu memiliki spasi dan cruft lain di nama file dan ini memecahkan masalah saya:

find . -type f -name "*.nzb.queued" |
sed -ne "s/^\(\(.*\).nzb.queued\)$/mv -v \"\1\" \"\2.nzb\"/p" |
sh

Ini berdasarkan jawaban Diomidis Spinellis.

Regex membuat satu grup untuk seluruh nama file, dan satu grup untuk bagian sebelum .nzb.queued dan kemudian membuat perintah pemindahan shell. Dengan senar yang dikutip. Ini juga menghindari pembuatan loop dalam skrip shell karena ini sudah dilakukan oleh sed.

Jerry Jacobs
sumber
Perlu dicatat bahwa ini hanya berlaku untuk GNU sed. Di BSDs, sed tidak akan menafsirkan -nopsi dengan cara yang sama.
ocodo