在Unix中基于模式重命名多个文件

330

目录中有多个以前缀fgh开头的文件,例如:

fghfilea
fghfileb
fghfilec

我想将它们所有重命名为以前缀 jkl 开头。是否有一种单个命令可以代替逐个重命名每个文件?


2
请在此处查看:http://theunixshell.blogspot.com/2013/01/bulk-renaming-of-files-in-unix.html - Vijay
相关问题:基于多个模式重命名文件的更好方法 - Michaël Le Barbier
26个回答

2
在Solaris上,你可以尝试使用以下方法:
for file in `find ./ -name "*TextForRename*"`; do 
    mv -f "$file" "${file/TextForRename/NewText}"
done

在循环内引用文件名可以得到半个分,但是 for file in $(find) 是根本性有缺陷的,无法通过引用修正。如果 find 返回 ./file name with spaces,则会得到一个 for 循环,其中包含 ./filenamewithspaces,即使在循环内部进行引用也无济于事(甚至不必要)。 - 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

这个脚本对我来说很有用,可以递归地重命名包含空格的目录/文件名:

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

注意此示例中的sed表达式,它替换了所有出现的;空格。当然,根据具体需求应该进行相应更改。


1

在我的 Mac 上,使用 Ruby 进行此操作要容易得多。以下是两个示例:

# 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
使用StringSolver工具(Windows和Linux bash),通过示例进行处理:
filter fghfilea ok fghreport ok notfghfile notok; mv --all --filter fghfilea jklfilea

它首先基于示例计算过滤器,其中输入是文件名,输出为(ok和notok,任意字符串)。如果filter选项为--auto或在此命令之后单独调用,则会创建一个名为ok的文件夹和一个名为notok的文件夹,并将文件分别推送到这些文件夹中。
然后使用该过滤器,mv命令是一种半自动移动方式,使用修饰符--auto后变为自动。通过--filter使用先前的过滤器,它找到从fghfileajklfilea的映射,然后将其应用于所有经过过滤的文件。

其他一行解决方案

做同样的事情的其他等效方法(每行都是等效的),所以你可以选择你喜欢的方式来完成它。

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"

多步解决方案

为了仔细查找命令是否执行良好,您可以输入以下内容:

filter fghfilea ok
filter fghfileb ok
filter fghfileb notok

当你确信筛选器是好的时候,执行第一步操作:

mv fghfilea jklfilea

如果您想测试并使用先前的过滤器,请键入:
mv --test --filter

如果转换不是您想要的(例如,即使使用mv --explain也发现有问题),您可以键入mv --clear以重新启动移动文件,或者添加更多示例mv input1 input2其中input1和input2是其他示例。
当您有信心时,只需键入:
mv --filter

完成了!所有重命名都使用过滤器完成。

免责声明:本作品是为学术目的而制作的,我是其中一位共同作者。很快可能还会有一个生成bash的功能。


1

我建议使用我的脚本来解决这个问题。它还有更改文件名编码和将组合变音符转换为预组合字符的选项。当我从我的 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
请注意,仅链接答案是不被鼓励的,SO答案应该是寻找解决方案的终点(而不是另一个参考站点,随着时间的推移往往会变得陈旧)。请考虑在此处添加独立的摘要,将链接作为参考。 - kleopatra
正如 @kleopatra 所预测的那样,链接随着时间的推移已经变得 陈旧无效 - y2k-shubham
1
@y2k-shubham,不再是这样了。 - André Kugland

1

我重命名批量文件的版本:

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

我喜欢在运行脚本之前进行验证的能力。 - port5432
这在包含空格、引号或其他 shell 元字符的文件名上会出现严重问题。 - tripleee

1
另一个可能的参数扩展
for f in fgh*; do mv -- "$f" "jkl${f:3}"; done

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

当你确认输出结果正确无误后,请移除--dry-run标志。


0
一个通用的脚本,可以在文件列表上运行 sed 表达式(将 sed 解决方案rename 解决方案 结合起来):
#!/bin/sh

e=$1
shift

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

通过传递一个sed表达式和文件列表来调用脚本,就像rename的一个版本:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接