Đổi tên nhiều tệp dựa trên mẫu trong Unix


248

Có nhiều tệp trong một thư mục bắt đầu bằng tiền tố fgh, ví dụ:

fghfilea
fghfileb
fghfilec

Tôi muốn đổi tên tất cả chúng để bắt đầu bằng tiền tố jkl. Có một lệnh duy nhất để làm điều đó thay vì đổi tên từng tệp riêng lẻ không?



Câu trả lời:


289

Có một số cách, nhưng sử dụng renamecó thể sẽ là dễ nhất.

Sử dụng một phiên bản của rename:

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

Sử dụng phiên bản khác của rename(giống như câu trả lời của Judy2K ):

rename fgh jkl fgh*

Bạn nên kiểm tra trang người dùng của nền tảng của bạn để xem áp dụng nào ở trên.


7
+1 Thậm chí còn không biết về đổi tên ... Bây giờ tôi có thể ngừng sử dụng vòng lặp for với mv và sed ... Cảm ơn!
balpha

2
Bạn đang liên kết đến một cái khác renamethì bạn đang hiển thị cú pháp cho unixhelp.ed.ac.uk/CGI/man-cgi?rename là một cái khác
Hasturkun

7
Không có mặt trên tất cả các hệ thống * nix. Không phải trên Max OS X cho một và không có gói nào trong fink để có được nó. Không nhìn vào MacPorts.
dmckee --- ex-moderator mèo con

6
AFAICT, renamedường như là một tập lệnh hoặc tiện ích cụ thể của Linux. Nếu bạn quan tâm đến tính di động, vui lòng tiếp tục sử dụng sedvà các vòng lặp hoặc tập lệnh Perl nội tuyến.
D.Shawley

33
brew install renametrên OS X :)
sam

117

Đây là cách sedmvcó thể được sử dụng cùng nhau để đổi tên:

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

Theo nhận xét bên dưới, nếu tên tệp có khoảng trắng trong đó, dấu ngoặc kép có thể cần bao quanh hàm phụ trả về tên để di chuyển tệp đến:

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

1
Rất gần. Lưu ý rằng bạn chỉ muốn khớp với lần xuất hiện đầu tiên của fgh: 's / ^ fgh / jkl / g' (Dấu mũ tạo ra tất cả sự khác biệt).
Stephan202

2
Chỉ vì mục đích chính xác ... Bạn có nghĩa là "fgh ở đầu tên", không phải "sự xuất hiện đầu tiên của fgh". / ^ fgh / sẽ khớp với "fghi", nhưng không khớp với "efgh".
Dave Sherohman

@Stephan, Đó là một lỗi đánh máy trên phần của tôi (đã sửa nó).
nik

5
Nếu bạn không có quyền truy cập để "đổi tên" thì điều này rất tốt. Mã có thể yêu cầu báo giá nếu tên tệp của bạn bao gồm khoảng trắng. for f in fgh*; do mv "$f" "$(echo $f | sed 's/^fgh/jkl/g')"; done
Dave Nelson

1
@nik Nếu không có trích dẫn, việc đổi tên danh sách các tệp này sẽ gây ra lỗi : touch fghfilea fghfileb fghfilec fghfile\ d. Tôi vui lòng đề nghị xem xét nhận xét @DaveNelson.
luissquall

75

đổi tên có thể không có trong mọi hệ thống. Vì vậy, nếu bạn không có nó, hãy sử dụng shell ví dụ này trong bash shell

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

1
Trong giải pháp này, sedlệnh không bắt buộc. Câu hỏi này đơn giản hơn câu trả lời của @ nik.
Halil

xxx là viết tắt của sự thay thế? trong trường hợp của poster gốc, đó sẽ là "jkl"
Awinad

1
Các giải pháp sạch nhất của tất cả chúng.
Đầu MT

3
Có lẽ chỉ ra rõ ràng hơn rằng đây là một phần mở rộng Bash không tồn tại trong POSIX shhoặc nói chung trong các shell khác.
tripleee

Ngọt ngào - có rất nhiều góc khuất trong thế giới Unix - chưa từng thấy cái này trước đây.
wcochran

40

Sử dụng mmv :

mmv "fgh*" "jkl#1"

1
Wow, đây là một cách tuyệt vời và đơn giản để giải quyết vấn đề! Vui mừng được giới thiệu đến mmv, cảm ơn!
Hendeca

1
Cảm ơn!!! Chưa bao giờ nghe nói về mmv trước đây. Chỉ cần cài đặt và chơi với nó - đơn giản, mạnh mẽ.
Nick Rice

3
nếu bạn muốn đổi tên hàng loạt sử dụng đệ quy ;kết hợp với #1. ví dụ:mmv ";fgh*" "#1jkl#2"
Alp

Giải pháp rất thanh lịch!
Học sinh nhỏ của Fermat

1
Chính xác những gì tôi cần. Chụp các nhóm bằng ' ' và gọi lại cho họ bằng # 1, # 2, ... EX: mmv "chương trình của tôi ep 1080p. *" "My.show. # 1. # 2" = my.show.001.avi
Lundy

20

Có nhiều cách để làm điều đó (không phải tất cả trong số này sẽ hoạt động trên tất cả các hệ thống unixy):

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

    § có thể được thay thế bởi bất cứ điều gì bạn thấy thuận tiện. Bạn cũng có thể làm điều này find -execnhưng nó hoạt động khác biệt tinh tế trên nhiều hệ thống, vì vậy tôi thường tránh điều đó

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

    Thô lỗ nhưng hiệu quả như họ nói

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

    Thực sự đẹp, nhưng đổi tên không có trên BSD, đây là hệ thống unix phổ biến nhất afaik.

  • rename fgh jkl fgh*

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

    Nếu bạn khăng khăng sử dụng Perl, nhưng không có đổi tên trên hệ thống của bạn, bạn có thể sử dụng quái vật này.

Một số trong số đó là một chút phức tạp và danh sách không đầy đủ, nhưng bạn sẽ tìm thấy những gì bạn muốn ở đây cho hầu hết các hệ thống unix.


2
tôi yêu loại quái vật này
lefakir

vâng, tôi vẫn còn một điểm yếu cho perl nữa :)
iwein


Quái vật Perl ở đây sẽ hoạt động hoàn hảo nếu bạn có không gian trong tên tệp của mình.
mlerley

13
rename fgh jkl fgh*

6
Trên máy của tôi, điều này tạo ra lỗi 'Bareword "fgh" không được phép trong khi "subs subs" được sử dụng tại (eval 1) dòng 1.'
Stephan202

@ Stephan202, máy của bạn là gì?
nik

Ubuntu 8.10 (perl v5.10.0 / 2009-06-26)
Stephan202

@ Stephan202 Tôi có vấn đề tương tự, bạn đã giải quyết chưa? (7 năm sau)
tên

1
@whossname: xin lỗi, thực sự không thể nhớ. (Trả lời chậm do kỳ nghỉ.)
Stephan202

8

Sử dụng find, xargssed:

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

Nó phức tạp hơn giải pháp của @ nik nhưng nó cho phép đổi tên các tệp theo cách đệ quy. Ví dụ, cấu trúc,

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

sẽ được chuyển đổi sang đây

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

Chìa khóa để làm cho nó hoạt động xargsgọi shell từ xargs .


1
Tôi sẽ đăng một cái gì đó như thế này, nhưng tôi sẽ mất một giờ để nhận được lệnh đúng
Juan Mendes

3

Để cài đặt tập lệnh đổi tên Perl :

sudo cpan install File::Rename

hai sự đổi tên như được đề cập trong các bình luận trong câu trả lời của Stephan202. Các bản phân phối dựa trên Debian có đổi tên Perl . Các bản phân phối Redhat / vòng / phút có tên C đổi tên .
OS X không có cài đặt mặc định (ít nhất là trong 10.8), Windows / Cygwin cũng không.


3

Đây là một cách để làm điều đó bằng cách sử dụng dòng lệnh Groovy:

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

2

Trên Solaris bạn có thể thử:

for file in `find ./ -name "*TextForRename*"`; do 
    mv -f "$file" "${file/TextForRename/NewText}"
done

Bạn nhận được một nửa điểm để trích dẫn tên tệp trong vòng lặp, nhưng for file in $(find)về cơ bản là thiếu sót và không thể sửa chữa bằng trích dẫn. Nếu findlợi nhuận ./file name with spacesbạn sẽ nhận được một forvòng lặp qua ./file, name, with, và spacesvà không có số lượng trích dẫn bên trong vòng lặp sẽ giúp (hoặc thậm chí là cần thiết).
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

2

Kịch bản này hoạt động với tôi để đổi tên đệ quy với các thư mục / tên tệp có thể chứa khoảng trắng:

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

Lưu ý sedbiểu thức trong ví dụ này thay thế tất cả các lần xuất hiện ;với không gian . Điều này tất nhiên nên được thay thế theo nhu cầu cụ thể.


Cảm ơn rất nhiều ! Kịch bản tuyệt vời
Walter Schrabmair

1

Sử dụng các công cụ StringSolver (bash windows & Linux) xử lý bằng các ví dụ:

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

Đầu tiên, nó tính toán một bộ lọc dựa trên các ví dụ , trong đó đầu vào là tên tệp và đầu ra (ok và notok, các chuỗi tùy ý). Nếu bộ lọc có tùy chọn --auto hoặc được gọi một mình sau lệnh này, nó sẽ tạo một thư mục okvà thư mục notokvà đẩy các tệp tương ứng với chúng.

Sau đó, sử dụng bộ lọc, mvlệnh là một bước di chuyển bán tự động trở thành tự động với công cụ sửa đổi --auto. Sử dụng bộ lọc trước nhờ vào --filter, nó tìm thấy ánh xạ từ fghfileađến jklfileavà sau đó áp dụng nó trên tất cả các tệp được lọc.


Các giải pháp một dòng khác

Các cách tương đương khác để làm tương tự (mỗi dòng là tương đương), vì vậy bạn có thể chọn cách làm yêu thích của mình.

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"

Giải pháp nhiều bước

Để cẩn thận tìm xem các lệnh có hoạt động tốt không, bạn có thể gõ như sau:

filter fghfilea ok
filter fghfileb ok
filter fghfileb notok

và khi bạn tự tin rằng bộ lọc tốt, hãy thực hiện bước đầu tiên:

mv fghfilea jklfilea

Nếu bạn muốn kiểm tra và sử dụng bộ lọc trước đó, hãy nhập:

mv --test --filter

Nếu chuyển đổi không phải là điều bạn muốn (ví dụ: ngay cả khi mv --explainbạn thấy có gì đó không đúng), bạn có thể nhập mv --clearđể khởi động lại các tệp đang di chuyển hoặc thêm các ví dụ khácmv input1 input2 trong đó input1 và input2 là các ví dụ khác

Khi bạn tự tin, chỉ cần gõ

mv --filter

và Voila! Tất cả việc đổi tên được thực hiện bằng bộ lọc.

TUYÊN BỐ TỪ CHỐI: Tôi là đồng tác giả của tác phẩm này được thực hiện cho mục đích học tập. Cũng có thể có một tính năng sản xuất bash sớm.


1

Nó dễ dàng hơn nhiều (trên máy Mac của tôi) để làm điều này trong Ruby. Dưới đây là 2 ví dụ:

# 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

1

Sử dụng renamer :

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

Xóa --dry-runcờ một khi bạn hài lòng, đầu ra có vẻ chính xác.


1

Phiên bản đổi tên tập tin đại chúng của tôi:

for i in *; do
    echo "mv $i $i"
done |
sed -e "s#from_pattern#to_pattern#g” > result1.sh
sh result1.sh

Tôi như khả năng để xác minh trước khi chạy kịch bản
ardochhigh

Điều này phá vỡ tuyệt vời trên tên tệp có chứa khoảng trắng, dấu ngoặc kép hoặc các siêu ký tự shell khác.
tripleee

1

Tôi sẽ khuyên bạn nên sử dụng kịch bản của riêng tôi, giải quyết vấn đề này. Nó cũng có các tùy chọn để thay đổi mã hóa tên tệp và để chuyển đổi kết hợp dấu phụ sang các ký tự được phân tách trước, một vấn đề tôi luôn gặp phải khi sao chép tệp từ máy Mac.

#!/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);
  }
}

2
Lưu ý rằng các câu trả lời chỉ liên kết không được khuyến khích, các câu trả lời SO phải là điểm cuối của tìm kiếm giải pháp (so với một điểm dừng khác của tài liệu tham khảo, có xu hướng bị cũ theo thời gian). Vui lòng xem xét việc thêm một bản tóm tắt độc lập ở đây, giữ liên kết làm tài liệu tham khảo.
kleopatra

Theo dự đoán của @kleopatra , liên kết đã nhận đượcstale over time
y2k-shubham

1
@ y2k-shubham, không còn nữa.
André von Kugland

0

Điều này làm việc cho tôi bằng cách sử dụng regrec:

Tôi muốn các tập tin được đổi tên như thế này:

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

usig, [az | _] + 0 * ([0-9] +. ) regrec trong đó ([0-9] +. ) là một chuỗi con nhóm để sử dụng trên lệnh đổi tên

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

Sản xuất:

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

Một vi dụ khac:

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

Sản xuất:

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

Cảnh báo, cẩn thận.


0

Tôi đã viết tập lệnh này để tìm kiếm tất cả các tệp .mkv đệ quy đổi tên các tệp tìm thấy thành .avi. Bạn có thể tùy chỉnh nó để bạn có. Tôi đã thêm một số thứ khác như nhận thư mục tệp, phần mở rộng, tên tệp từ đường dẫn tệp chỉ cần bạn cần tham khảo một cái gì đó trong tương lai.

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;

0

Một tập lệnh chung để chạy một sedbiểu thức trên danh sách các tệp (kết hợp sedgiải pháp với renamegiải pháp ):

#!/bin/sh

e=$1
shift

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

Gọi bằng cách chuyển tập lệnh một sedbiểu thức và sau đó bất kỳ danh sách tệp nào, giống như một phiên bản của rename:

script.sh 's/^fgh/jkl/' fgh*

0

Bạn cũng có thể sử dụng kịch bản dưới đây. nó rất dễ chạy trên thiết bị đầu cuối ...

// Đổi tên nhiều tệp cùng một lúc

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

Thí dụ:-

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.