Ganti nama banyak file berdasarkan pola di Unix

248

Ada beberapa file dalam direktori yang dimulai dengan awalan fgh, misalnya:

fghfilea
fghfileb
fghfilec

Saya ingin mengubah nama semuanya menjadi awalan jkl. Apakah ada satu perintah untuk melakukan itu daripada mengganti nama setiap file satu per satu?

jww
sumber
1
periksa di sini: theunixshell.blogspot.com/2013/01/...
Vijay
Pertanyaan terkait: Cara yang lebih baik untuk mengganti nama file berdasarkan beberapa pola .
Michael Le Barbier Grünewald

Jawaban:

289

Ada beberapa cara, tetapi menggunakan renamemungkin akan menjadi yang paling mudah.

Menggunakan satu versi rename:

rename 's/^fgh/jkl/' fgh*

Menggunakan versi lain rename(sama dengan jawaban Judy2K ):

rename fgh jkl fgh*

Anda harus memeriksa halaman manual platform Anda untuk melihat mana dari yang berlaku di atas.

Stephan202
sumber
7
+1 Bahkan tidak tahu tentang mengganti nama ... Sekarang saya bisa berhenti menggunakan for for dengan mv dan sed ... Terima kasih!
balpha
2
Anda menautkan ke yang berbeda renamemaka Anda menunjukkan sintaks untuk unixhelp.ed.ac.uk/CGI/man-cgi?rename adalah yang lain
Hasturkun
7
Tidak ada pada semua sistem * nix. Bukan pada Max OS X untuk satu, dan tidak ada paket di fink untuk mendapatkannya. Belum melihat MacPorts.
dmckee --- ex-moderator kitten
6
AFAICT, renametampaknya merupakan skrip atau utilitas khusus Linux. Jika Anda sama sekali peduli tentang portabilitas, silakan terus menggunakan seddan loop atau skrip Perl inline.
D.Shawley
33
brew install renamepada OS X :)
sam
117

Ini adalah bagaimana seddan mvdapat digunakan bersama untuk melakukan rename:

for f in fgh*; do mv "$f" $(echo "$f" | sed 's/^fgh/jkl/g'); done

Seperti komentar di bawah ini, jika nama file memiliki spasi di dalamnya, tanda kutip mungkin perlu mengelilingi sub-fungsi yang mengembalikan nama untuk memindahkan file ke:

for f in fgh*; do mv "$f" "$(echo $f | sed 's/^fgh/jkl/g')"; done
nik
sumber
1
Sangat dekat. Perhatikan bahwa Anda hanya ingin mencocokkan kejadian pertama fgh: 's / ^ fgh / jkl / g' (Tanda sisipan membuat perbedaan).
Stephan202
2
Demi ketepatan ... Maksudmu "fgh di awal nama", bukan "kejadian pertama fgh". / ^ fgh / akan cocok dengan "fghi", tetapi tidak "efgh".
Dave Sherohman
@Stephan, Itu salah ketik di pihak saya (memperbaikinya).
nik
5
Jika Anda tidak memiliki akses ke "ganti nama" ini berfungsi dengan baik. Kode ini mungkin memerlukan penawaran jika nama file Anda menyertakan spasi. for f in fgh*; do mv "$f" "$(echo $f | sed 's/^fgh/jkl/g')"; done
Dave Nelson
1
@nik Tanpa tanda kutip mengubah nama ini daftar file akan melemparkan kesalahan: touch fghfilea fghfileb fghfilec fghfile\ d. Saya sarankan untuk mempertimbangkan komentar @DaveNelson.
luissquall
75

mengganti nama mungkin tidak ada di setiap sistem. jadi jika Anda tidak memilikinya, gunakan shell contoh ini di bash shell

for f in fgh*; do mv "$f" "${f/fgh/xxx}";done
ghostdog74
sumber
1
Dalam solusi ini, sedperintah tidak diperlukan. Yang ini lebih sederhana dari jawaban @ nik.
Halil
xxx singkatan dari penggantian? dalam hal poster aslinya, itu adalah "jkl"
Awinad
1
Solusi terbersih dari semuanya.
MT Head
3
Mungkin dengan lebih tegas menunjukkan bahwa ini adalah ekstensi Bash yang tidak ada di POSIX shatau umumnya di shell lain.
tripleee
Manis - ada begitu banyak sudut yang tidak jelas di dunia Unix - belum pernah melihat yang satu ini sebelumnya.
wcochran
40

Menggunakan mmv :

mmv "fgh*" "jkl#1"
Lee Netherton
sumber
1
Wow, ini adalah cara yang sangat baik dan sederhana untuk menyelesaikan masalah! Senang diperkenalkan ke mmv, terima kasih!
Hendeca
1
Terima kasih!!! Belum pernah mendengar mmv sebelumnya. Baru diinstal dan bermain dengannya - sederhana, kuat.
Nick Rice
3
jika Anda ingin batch mengubah nama digunakan secara rekursif ;bersama #1. contoh:mmv ";fgh*" "#1jkl#2"
Alp
Solusi yang sangat elegan!
Mahasiswa Kecil Fermat
1
Apa yang saya butuhkan. Abadikan grup dengan ' ' dan panggil mereka kembali dengan # 1, # 2, ... EX: mmv "pertunjukkan saya 1080p. *" "My.show. # 1. # 2" = my.show.001.avi
Lundy
20

Ada banyak cara untuk melakukannya (tidak semua ini akan bekerja pada semua sistem unixy):

  • ls | cut -c4- | xargs -I§ mv fgh§ jkl§

    § dapat diganti dengan apa pun yang Anda rasa nyaman. Anda dapat melakukan ini dengan find -execterlalu tetapi itu berperilaku agak berbeda pada banyak sistem, jadi saya biasanya menghindarinya

  • for f in fgh*; do mv "$f" "${f/fgh/jkl}";done

    Mentah tetapi efektif seperti yang mereka katakan

  • rename 's/^fgh/jkl/' fgh*

    Cukup cantik, tetapi penggantian nama tidak ada pada BSD, yang merupakan sistem unix yang paling umum.

  • rename fgh jkl fgh*

  • ls | perl -ne 'chomp; next unless -e; $o = $_; s/fgh/jkl/; next if -e; rename $o, $_';

    Jika Anda bersikeras menggunakan Perl, tetapi tidak ada mengganti nama pada sistem Anda, Anda dapat menggunakan monster ini.

Beberapa di antaranya agak berbelit-belit dan daftarnya jauh dari lengkap, tetapi Anda akan menemukan apa yang Anda inginkan di sini untuk hampir semua sistem unix.

iwein
sumber
2
Saya suka monster seperti ini!
lefakir
ya, saya masih memiliki titik lemah untuk perl juga :)
iwein
Monster Perl di sini bekerja dengan sempurna jika Anda memiliki ruang di nama file Anda.
mlerley
13
rename fgh jkl fgh*
Judy2K
sumber
6
Di mesin saya ini menghasilkan kesalahan 'Bareword "fgh" tidak diizinkan saat "subs ketat" digunakan di (eval 1) baris 1.'
Stephan202
@ Stephan202, apa mesin Anda?
nik
Ubuntu 8.10 (perl v5.10.0 / 2009-06-26)
Stephan202
@ Stephan202 Saya memiliki masalah yang sama, apakah Anda menyelesaikan? (7 tahun kemudian)
whossname
1
@whossname: maaf, benar-benar tidak ingat. (Balasan lambat karena hari libur.)
Stephan202
8

Menggunakan find, xargsdan sed:

find . -name "fgh*" -type f -print0 | xargs -0 -I {} sh -c 'mv "{}" "$(dirname "{}")/`echo $(basename "{}") | sed 's/^fgh/jkl/g'`"'

Ini lebih kompleks daripada solusi @ nik tetapi memungkinkan untuk mengubah nama file secara rekursif. Misalnya, struktur,

.
├── fghdir
│   ├── fdhfilea
│   └── fghfilea
├── fghfile\ e
├── fghfilea
├── fghfileb
├── fghfilec
└── other
    ├── fghfile\ e
    ├── fghfilea
    ├── fghfileb
    └── fghfilec

akan diubah menjadi ini,

.
├── fghdir
│   ├── fdhfilea
│   └── jklfilea
├── jklfile\ e
├── jklfilea
├── jklfileb
├── jklfilec
└── other
    ├── jklfile\ e
    ├── jklfilea
    ├── jklfileb
    └── jklfilec

Kunci untuk membuatnya bekerja xargsadalah dengan memanggil shell dari xargs .

Luissquall
sumber
1
Saya akan memposting sesuatu seperti ini, tetapi perlu waktu satu jam untuk mendapatkan perintah yang tepat
Juan Mendes
3

Untuk menginstal skrip rename Perl :

sudo cpan install File::Rename

Ada dua nama seperti yang disebutkan dalam komentar dalam jawaban Stephan202. Distro berbasis Debian memiliki nama Perl . Redhat / rpm distro memiliki C rename .
OS X tidak memiliki satu yang diinstal secara default (setidaknya dalam 10.8), begitu pula Windows / Cygwin.

Sam Inverso
sumber
3

Berikut cara untuk melakukannya menggunakan Groovy baris perintah:

groovy -e 'new File(".").eachFileMatch(~/fgh.*/) {it.renameTo(it.name.replaceFirst("fgh", "jkl"))}'
jesseplymale
sumber
2

Pada Solaris Anda dapat mencoba:

for file in `find ./ -name "*TextForRename*"`; do 
    mv -f "$file" "${file/TextForRename/NewText}"
done
Andrei Aleksandrov
sumber
Anda mendapatkan setengah poin untuk mengutip nama file di dalam loop, tetapi pada for file in $(find)dasarnya cacat dan tidak dapat diperbaiki dengan mengutip. Jika findpengembalian ./file name with spacesAnda akan mendapatkan forloop di atas ./file, name, with, dan spacesdan tidak ada jumlah mengutip dalam loop akan membantu (atau bahkan diperlukan).
tripleee
2
#!/bin/sh

#replace all files ended witn .f77 to .f90 in a directory

for filename in *.f77
do 
    #echo $filename
    #b= echo $filename | cut -d. -f1
    #echo $b    
    mv "${filename}" "${filename%.f77}.f90"    
done
Suragini
sumber
2

Skrip ini berfungsi untuk saya untuk pengubahan nama secara rekursif dengan direktori / nama file yang mungkin mengandung spasi putih:

find . -type f -name "*\;*" | while read fname; do
    dirname=`dirname "$fname"`
    filename=`basename "$fname"`
    newname=`echo "$filename" | sed -e "s/;/ /g"`
    mv "${dirname}/$filename" "${dirname}/$newname"
done

Perhatikan sedungkapan yang dalam contoh ini menggantikan semua kemunculan ;dengan spasi . Ini tentu saja harus diganti sesuai dengan kebutuhan spesifik.

nielsen
sumber
Terima kasih banyak ! Naskah yang bagus
Walter Schrabmair
1

Menggunakan alat StringSolver (windows & Linux bash) yang memproses dengan contoh:

filter fghfilea ok fghreport ok notfghfile notok; mv --all --filter fghfilea jklfilea

Pertama menghitung filter berdasarkan contoh , di mana input adalah nama file dan output (ok dan notok, string sewenang-wenang). Jika filter memiliki opsi --auto atau dipanggil sendiri setelah perintah ini, itu akan membuat folder okdan folder notokdan mendorong file masing-masing ke mereka.

Kemudian menggunakan filter, mvperintahnya adalah gerakan semi-otomatis yang menjadi otomatis dengan modifier --auto. Menggunakan filter sebelumnya berkat --filter, ia menemukan pemetaan dari fghfileake jklfileadan kemudian menerapkannya pada semua file yang difilter.


Solusi satu baris lainnya

Cara lain yang setara untuk melakukan hal yang sama (setiap baris sama), sehingga Anda dapat memilih cara favorit Anda untuk melakukannya.

filter fghfilea ok fghreport ok notfghfile notok; mv --filter fghfilea jklfilea; mv
filter fghfilea ok fghreport ok notfghfile notok; auto --all --filter fghfilea "mv fghfilea jklfilea"
# Even better, automatically infers the file name
filter fghfilea ok fghreport ok notfghfile notok; auto --all --filter "mv fghfilea jklfilea"

Solusi multi-langkah

Untuk mengetahui apakah perintahnya berkinerja baik, Anda dapat mengetik berikut ini:

filter fghfilea ok
filter fghfileb ok
filter fghfileb notok

dan ketika Anda yakin bahwa filternya bagus, lakukan langkah pertama:

mv fghfilea jklfilea

Jika Anda ingin menguji, dan menggunakan filter sebelumnya, ketik:

mv --test --filter

Jika transformasi bukan yang Anda inginkan (mis. Walaupun mv --explainAnda melihat ada sesuatu yang salah), Anda dapat mengetik mv --clearuntuk memulai ulang file yang bergerak, atau menambahkan lebih banyak contoh di mv input1 input2mana input1 dan input2 adalah contoh lain

Saat Anda percaya diri, ketik saja

mv --filter

dan voila! Semua penggantian nama dilakukan menggunakan filter.

PENOLAKAN: Saya adalah rekan penulis dari karya ini yang dibuat untuk tujuan akademik. Mungkin juga ada fitur penghasil bash segera.

Mikaël Mayer
sumber
1

Jauh lebih mudah (di Mac saya) untuk melakukan ini di Ruby. Berikut adalah 2 contoh:

# for your fgh example. renames all files from "fgh..." to "jkl..."
files = Dir['fgh*']

files.each do |f|
  f2 = f.gsub('fgh', 'jkl')
  system("mv #{f} #{f2}")
end

# renames all files in directory from "021roman.rb" to "021_roman.rb"
files = Dir['*rb'].select {|f| f =~ /^[0-9]{3}[a-zA-Z]+/}

files.each do |f|
  f1 = f.clone
  f2 = f.insert(3, '_')
  system("mv #{f1} #{f2}")
end
Raymond Gan
sumber
1

Menggunakan renamer :

$ renamer --find /^fgh/ --replace jkl * --dry-run

Hapus --dry-runbendera setelah Anda senang hasilnya terlihat benar.

Lloyd
sumber
1

Versi saya mengubah nama file massal:

for i in *; do
    echo "mv $i $i"
done |
sed -e "s#from_pattern#to_pattern#g” > result1.sh
sh result1.sh
mdev
sumber
Saya menyukai kemampuan untuk memverifikasi sebelum menjalankan skrip
ardochhigh
Ini memecah megah pada nama file yang mengandung spasi, tanda kutip, atau karakter meta shell lainnya.
tripleee
1

Saya akan merekomendasikan menggunakan skrip saya sendiri, yang memecahkan masalah ini. Ia juga memiliki opsi untuk mengubah pengkodean nama file, dan untuk mengkonversi menggabungkan diakritik ke karakter yang dikomposisi, masalah yang selalu saya miliki ketika saya menyalin file dari Mac saya.

#!/usr/bin/perl

# Copyright (c) 2014 André von Kugland

# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.

$help_msg =
"rename.pl, a script to rename files in batches, using Perl
           expressions to transform their names.
Usage:
    rename.pl [options] FILE1 [FILE2 ...]
Where options can be:
    -v                      Verbose.
    -vv                     Very verbose.
    --apply                 Really apply modifications.
    -e PERLCODE             Execute PERLCODE. (e.g. 's/a/b/g')
    --from-charset=CS       Source charset. (e.g. \"iso-8859-1\")
    --to-charset=CS         Destination charset. (e.g. \"utf-8\")
    --unicode-normalize=NF  Unicode normalization form. (e.g. \"KD\")
    --basename              Modifies only the last element of the path.
";

use Encode;
use Getopt::Long;
use Unicode::Normalize 'normalize';
use File::Basename;
use I18N::Langinfo qw(langinfo CODESET);

Getopt::Long::Configure ("bundling");

# ----------------------------------------------------------------------------------------------- #
#                                           Our variables.                                        #
# ----------------------------------------------------------------------------------------------- #

my $apply = 0;
my $verbose = 0;
my $help = 0;
my $debug = 0;
my $basename = 0;
my $unicode_normalize = "";
my @scripts;
my $from_charset = "";
my $to_charset = "";
my $codeset = "";

# ----------------------------------------------------------------------------------------------- #
#                                        Get cmdline options.                                     #
# ----------------------------------------------------------------------------------------------- #

$result = GetOptions ("apply" => \$apply,
                      "verbose|v+" => \$verbose,
                      "execute|e=s" => \@scripts,
                      "from-charset=s" => \$from_charset,
                      "to-charset=s" => \$to_charset,
                      "unicode-normalize=s" => \$unicode_normalize,
                      "basename" => \$basename,
                      "help|h|?" => \$help,
                      "debug" => \$debug);

# If not going to apply, then be verbose.
if (!$apply && $verbose == 0) {
  $verbose = 1;
}

if ((($#scripts == -1)
  && (($from_charset eq "") || ($to_charset eq ""))
  && $unicode_normalize eq "")
  || ($#ARGV == -1) || ($help)) {
  print $help_msg;
  exit(0);
}

if (($to_charset ne "" && $from_charset eq "")
  ||($from_charset eq "" && $to_charset ne "")
  ||($to_charset eq "" && $from_charset eq "" && $unicode_normalize ne "")) {
  $codeset = langinfo(CODESET);
  $to_charset = $codeset if $from_charset ne "" && $to_charset eq "";
  $from_charset = $codeset if $from_charset eq "" && $to_charset ne "";
}

# ----------------------------------------------------------------------------------------------- #
#         Composes the filter function using the @scripts array and possibly other options.       #
# ----------------------------------------------------------------------------------------------- #

$f = "sub filterfunc() {\n    my \$s = shift;\n";
$f .= "    my \$d = dirname(\$s);\n    my \$s = basename(\$s);\n" if ($basename != 0);
$f .= "    for (\$s) {\n";
$f .= "        $_;\n" foreach (@scripts);   # Get scripts from '-e' opt. #
# Handle charset translation and normalization.
if (($from_charset ne "") && ($to_charset ne "")) {
  if ($unicode_normalize eq "") {
    $f .= "        \$_ = encode(\"$to_charset\", decode(\"$from_charset\", \$_));\n";
  } else {
    $f .= "        \$_ = encode(\"$to_charset\", normalize(\"$unicode_normalize\", decode(\"$from_charset\", \$_)));\n"
  }
} elsif (($from_charset ne "") || ($to_charset ne "")) {
    die "You can't use `from-charset' nor `to-charset' alone";
} elsif ($unicode_normalize ne "") {
  $f .= "        \$_ = encode(\"$codeset\", normalize(\"$unicode_normalize\", decode(\"$codeset\", \$_)));\n"
}
$f .= "    }\n";
$f .= "    \$s = \$d . '/' . \$s;\n" if ($basename != 0);
$f .= "    return \$s;\n}\n";
print "Generated function:\n\n$f" if ($debug);

# ----------------------------------------------------------------------------------------------- #
#                 Evaluates the filter function body, so to define it in our scope.               #
# ----------------------------------------------------------------------------------------------- #

eval $f;

# ----------------------------------------------------------------------------------------------- #
#                  Main loop, which passes names through filters and renames files.               #
# ----------------------------------------------------------------------------------------------- #

foreach (@ARGV) {
  $old_name = $_;
  $new_name = filterfunc($_);

  if ($old_name ne $new_name) {
    if (!$apply or (rename $old_name, $new_name)) {
      print "`$old_name' => `$new_name'\n" if ($verbose);
    } else {
      print "Cannot rename `$old_name' to `$new_name'.\n";
    }
  } else {
    print "`$old_name' unchanged.\n" if ($verbose > 1);
  }
}
André von Kugland
sumber
2
Perhatikan bahwa jawaban hanya tautan tidak disarankan, jawaban SO harus menjadi titik akhir pencarian solusi (vs. persinggahan referensi lainnya, yang cenderung menjadi basi seiring waktu). Harap pertimbangkan untuk menambahkan sinopsis mandiri di sini, dengan menjaga tautan sebagai referensi.
kleopatra
Seperti yang diprediksi oleh @kleopatra , tautannya telah diperolehstale over time
y2k-shubham
1
@ y2k-shubham, tidak lagi.
André von Kugland
0

Ini bekerja untuk saya menggunakan regexp:

Saya ingin berganti nama file seperti ini:

file0001.txt -> 1.txt
ofile0002.txt -> 2.txt 
f_i_l_e0003.txt -> 3.txt

usig [az | _] + 0 * ([0-9] +. ) regexp di mana ([0-9] +. ) adalah substring grup untuk digunakan pada perintah rename

ls -1 | awk 'match($0, /[a-z|\_]+0*([0-9]+.*)/, arr) { print   arr[0]  " "  arr[1] }'|xargs  -l mv

Menghasilkan:

mv file0001.txt 1.txt
mv ofile0002.txt 2.txt
mv f_i_l_e0003.txt 3.txt

Contoh lain:

file001abc.txt -> abc1.txt
ofile0002abcd.txt -> abcd2.txt 

ls -1 | awk 'match($0, /[a-z|\_]+0*([0-9]+.*)([a-z]+)/, arr) { print   arr[0]  " "  arr[2] arr[1] }'|xargs  -l mv

Menghasilkan:

  mv file001abc.txt abc1.txt
  mv ofile0002abcd.txt abcd2.txt 

Peringatan, berhati-hatilah.

Steven Lizarazo
sumber
0

Saya menulis skrip ini untuk mencari semua file .mkv secara rekursif mengubah nama file yang ditemukan menjadi .avi. Anda dapat menyesuaikannya dengan kebutuhan Anda. Saya telah menambahkan beberapa hal lain seperti mendapatkan direktori file, ekstensi, nama file dari jalur file hanya jika Anda perlu merujuk ke sesuatu di masa depan.

find . -type f -name "*.mkv" | while read fp; do 
fd=$(dirname "${fp}");
fn=$(basename "${fp}");
ext="${fn##*.}";
f="${fn%.*}";
new_fp="${fd}/${f}.avi"
mv -v "$fp" "$new_fp" 
done;
David Okwii
sumber
0

Skrip generik untuk menjalankan sedekspresi pada daftar file (menggabungkan sedsolusi dengan renamesolusi ):

#!/bin/sh

e=$1
shift

for f in $*; do
    fNew=$(echo "$f" | sed "$e")
    mv "$f" "$fNew";
done

Aktifkan dengan memberikan sedekspresi pada skrip , lalu daftar file apa saja, seperti versi rename:

script.sh 's/^fgh/jkl/' fgh*
palswim
sumber
0

Anda juga dapat menggunakan skrip di bawah ini. sangat mudah dijalankan di terminal ...

// Ganti nama banyak file sekaligus

for file in  FILE_NAME*
do
    mv -i "${file}" "${file/FILE_NAME/RENAMED_FILE_NAME}"
done

Contoh:-

for file in  hello*
do
    mv -i "${file}" "${file/hello/JAISHREE}"
done
Sanjay Singh
sumber
0

Perluasan parameter lain yang mungkin :

for f in fgh*; do mv -- "$f" "jkl${f:3}"; done
Pesa
sumber