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

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

fghfilea
fghfileb
fghfilec

我想将它们全部重命名为前缀jkl开头。是否有一个单独的命令来执行该操作,而不是逐个重命名每个文件?

310233 次浏览

有几种方法,但使用rename可能是最简单的。

使用一个版本的rename:

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

使用另一个版本的rename(与Judy2K的回答相同):

rename fgh jkl fgh*

您应该检查您的平台的手册页,以确定上述哪一种方法适用。

rename fgh jkl fgh*

这就是如何一起使用sedmv来重命名:

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

根据下面的注释,如果文件名中有空格,则引号可能需要环绕返回文件名的子函数来将文件移动到:

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

rename可能不是在每个系统中。如果没有,就用壳层 这个例子在bash shell

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

有很多方法可以做到这一点(并不是所有的方法都适用于所有的unix系统):

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

    §可以被任何你觉得方便的东西所取代。你也可以用find -exec这样做,但它在许多系统上的表现略有不同,所以我通常避免

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

    粗糙但有效,就像他们说的

  • < p > rename 's/^fgh/jkl/' fgh*

    真的很漂亮,但是重命名在BSD上是不存在的,而BSD是最常见的unix系统

  • rename fgh jkl fgh*

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

    如果您坚持使用Perl,但是您的系统上没有重命名,您可以使用这个monster.

其中一些有点复杂,列表还远远不够完整,但是您将在这里找到几乎所有unix系统所需的内容。

安装Perl 重命名脚本:

sudo cpan install File::Rename

在Stephan202的回答中有两个重命名。 基于Debian的发行版有Perl重命名. exeRedhat/rpm发行版有C重命名.
. conf文件 OS X默认没有安装(至少在10.8中),Windows/Cygwin也没有。< / p >

这里有一个使用命令行Groovy的方法:

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

使用重命名:

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

一旦你对输出看起来正确感到满意,就删除--dry-run标志。

我建议使用我自己的脚本,它可以解决这个问题。它还有更改文件名编码的选项,以及将组合符号转换为预组合字符的选项,这是我从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);
}
}

使用mmv:

mmv "fgh*" "jkl#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生成功能。

(在我的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

使用findxargssed:

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

它比@nik的解决方案更复杂,但它允许递归地重命名文件。例如,结构,

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

会变成这样,

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

使它与xargs一起工作的关键是从xargs调用shell

#!/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

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

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

在Solaris上,您可以尝试:

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

这为我使用regexp工作:

我想要这样重命名文件:

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

usig [a-z|_]+0*([0-9]+.([0-9]+.))是用于重命名命令的组子字符串

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

生产:

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

另一个例子:

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

生产:

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

警告,小心点。

我编写了这个脚本来搜索所有.mkv文件,递归地将找到的文件重命名为.avi。您可以根据自己的需要定制它。我还添加了一些其他的东西,比如从文件路径中获取文件目录,扩展名,文件名,以防你将来需要引用一些东西。

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;

在文件列表上运行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*

您也可以使用下面的脚本。它很容易在终端上运行…

//一次重命名多个文件

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

例子:-

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

另一个可能的参数扩展:

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

这个脚本为我工作递归重命名目录/文件名可能包含空白:

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表达式,该表达式在本例中将所有出现的;替换为空格。这当然要根据具体需要进行更换。

通用命令为

find /path/to/files -name '<search>*' -exec bash -c 'mv $0 ${0/<search>/<replace>}' {} \;

其中<search><replace>应该分别替换为你的源和目标。

作为针对您的问题定制的更具体的示例(应该从与您的文件所在的文件夹运行),上面的命令看起来像这样:

find . -name 'gfh*' -exec bash -c 'mv $0 ${0/gfh/jkl}' {} \;

对于“预演”;在mv之前添加echo,这样你就可以看到生成了什么命令:

find . -name 'gfh*' -exec bash -c 'echo mv $0 ${0/gfh/jkl}' {} \;

这是find + sed + xargs解决方案的扩展版本。

原来的解决方案:

要求:搜索,修剪,正则表达式,重命名

  1. 我想重命名多个文件夹中的多个文件。
  2. 一些文件夹应该被删除。
  3. 我在cygwin上,不能让perl rename工作,这是最流行的解决方案所需要的(我假设它很慢,因为它似乎没有修剪选项?)

解决方案

  1. 使用find来有效地获取文件(使用修剪),并使用许多自定义选项。
  2. 使用sed替换正则表达式。
  3. 使用xargs将结果汇集到最终命令中。

例1:重命名*.js文件,忽略node_modules

这个示例查找文件并对找到的文件和重命名的文件进行回显。出于安全考虑,它目前没有移动任何东西。为此,你必须用mv替换echo

set -x # stop on error
set -e # verbose mode (echo all commands)


find "." -type f -not \( -path "**/node_modules/**" -prune \) -name "*.js" |
sed -nE "s/(.*)\/my(.*)/& \1\/YOUR\2/p" |
xargs -n 2 echo  # echo first (replace with `mv` later)

上面的脚本转换为:

./x/y/my-abc.js

到这个:

./x/y/YOUR-abc.js

溶液分解

    <李> find "." -type f -not \( -path "**/node_modules/**" -prune \) -name "*.js"
    • 搜索文件(-type f)。
    • -not部分排除了(重要的是没有遍历!)(众所周知的巨大)node_modules文件夹。
    • 文件名必须匹配"*.js"
    • 您可以添加更多的包含和排除子句。
    • <李>参考文献:
    <李> sed -nE "s/(.*)\/my\-(.*\.js)/& \1\/YOUR-\2/p"
    • 注意:sed总是需要一些时间来适应。
    • -E启用“扩展”;(即更现代的)正则表达式语法。
    • -n与后面的/p标志结合使用:-n隐藏所有结果,而/p只打印匹配的结果。这样,我们只看到/移动需要更改的文件,而忽略所有其他文件。
    • sed(和其他regex工具)替换正则表达式的格式总是:s/regex/replacement/FLAGS
    • 作为替换,&表示匹配的输入字符串。这将是mv的第一个参数。
    • <李>参考文献:
    <李> xargs -n 2 echo
    • 使用替换后的字符串(前两个字符串)运行命令echo
    • 参考文献:男人xargs

好运!