在 Unix 中根据模式重命名多个文件
Posted
技术标签:
【中文标题】在 Unix 中根据模式重命名多个文件【英文标题】:Rename multiple files based on pattern in Unix 【发布时间】:2010-11-08 08:39:28 【问题描述】:一个目录中有多个以fgh
前缀开头的文件,例如:
fghfilea
fghfileb
fghfilec
我想将它们全部重命名为以前缀jkl
开头。是否有一个命令可以做到这一点,而不是单独重命名每个文件?
【问题讨论】:
查看这里:theunixshell.blogspot.com/2013/01/… 相关问题:Better way to rename files based on multiple patterns. 【参考方案1】:通用命令是
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' \;
【讨论】:
【参考方案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
表达式,在此示例中,该表达式将所有出现的;
替换为空格。这个当然要根据具体需要更换。
【讨论】:
【参考方案3】:有几种方法,但使用rename
可能是最简单的。
使用一个版本的rename
:
rename 's/^fgh/jkl/' fgh*
使用另一个版本的rename
(与Judy2K's answer相同):
rename fgh jkl fgh*
您应该查看您平台的手册页以了解上述哪一项适用。
【讨论】:
+1 甚至不知道重命名...现在我可以停止使用带有 mv 和 sed 的 for 循环...谢谢! 您正在链接到另一个rename
,然后您正在显示 unixhelp.ed.ac.uk/CGI/man-cgi?rename 的语法是另一个
并非在所有 *nix 系统上都存在。一个人不在 Max OS X 上,也没有 fink 中的软件包来获得它。没看过 MacPorts。
AFAICT, rename
似乎是 Linux 特定的脚本或实用程序。如果您关心可移植性,请继续使用sed
和循环或内联 Perl 脚本。
brew install rename
在 OS X 上 :)【参考方案4】:
我建议使用我自己的脚本,它可以解决这个问题。它还具有更改文件名编码以及将组合变音符号转换为预组合字符的选项,这是我从 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);
【讨论】:
请注意 link-only answers 是不鼓励的,所以答案应该是寻找解决方案的终点(与另一个中途停留的参考相比,随着时间的推移往往会变得陈旧)。请考虑在此处添加独立的概要,并保留链接作为参考。 正如@kleopatra所预测的,链接已获得stale over time
@y2k-shubham ,不再。【参考方案5】:
另一个可能的parameter expansion:
for f in fgh*; do mv -- "$f" "jkl$f:3"; done
【讨论】:
【参考方案6】:#!/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
【讨论】:
【参考方案7】:我的海量文件重命名版本:
for i in *; do
echo "mv $i $i"
done |
sed -e "s#from_pattern#to_pattern#g” > result1.sh
sh result1.sh
【讨论】:
我喜欢在运行脚本之前进行验证 这在包含空格、引号或其他 shell 元字符的文件名上非常糟糕。【参考方案8】:使用renamer:
$ renamer --find /^fgh/ --replace jkl * --dry-run
如果您对输出看起来正确感到满意,请删除 --dry-run
标志。
【讨论】:
【参考方案9】:这就是sed
和mv
可以一起使用来重命名的方法:
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
【讨论】:
非常接近。请注意,您只想匹配第一次出现的 fgh: 's/^fgh/jkl/g' (插入符号会有所不同)。 为了精确起见...您的意思是“名称开头的 fgh”,而不是“第一次出现 fgh”。 /^fgh/ 将匹配“fghi”,但不匹配“efgh”。 如果您无权访问“重命名”,这非常有用。如果您的文件名包含空格,则代码可能需要引号。for f in fgh*; do mv "$f" "$(echo $f | sed 's/^fgh/jkl/g')"; done
@nik 如果没有引号,重命名此文件列表会引发错误:touch fghfilea fghfileb fghfilec fghfile\ d
。我建议您考虑@DaveNelson 的言论。
如果你只想匹配第一次出现,我想也不需要 g 修饰符。【参考方案10】:
您也可以使用以下脚本。在终端上运行非常容易...
//一次重命名多个文件
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
【讨论】:
【参考方案11】:在文件列表上运行sed
表达式的通用脚本(结合sed
solution 和rename
solution):
#!/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*
【讨论】:
【参考方案12】:我编写了这个脚本来搜索所有 .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;
【讨论】:
【参考方案13】:这对我使用正则表达式有用:
我希望像这样重命名文件:
file0001.txt -> 1.txt
ofile0002.txt -> 2.txt
f_i_l_e0003.txt -> 3.txt
使用 [az|_]+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
警告,小心。
【讨论】:
【参考方案14】:在 Solaris 上你可以试试:
for file in `find ./ -name "*TextForRename*"`; do
mv -f "$file" "$file/TextForRename/NewText"
done
【讨论】:
在循环中引用文件名inside会得到半分,但for file in $(find)
存在根本缺陷,无法通过引用来纠正。如果find
返回./file name with spaces
,您将在./file
、name
、with
和spaces
上得到一个for
循环,并且循环内的任何引用都无济于事(甚至没有必要)。 【参考方案15】:
使用find
、xargs
和sed
:
find . -name "fgh*" -type f -print0 | xargs -0 -I sh -c 'mv "" "$(dirname "")/`echo $(basename "") | sed 's/^fgh/jkl/g'`"'
它比@nik's solution 更复杂,但它允许递归地重命名文件。比如结构,
.
├── 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
一起工作的关键是invoke the shell from xargs。
【讨论】:
我打算发布这样的内容,但我需要一个小时才能获得正确的命令 这个答案允许的递归使它非常健壮——我只是快速重命名了深度嵌套层次结构中的数千个文件。【参考方案16】:在 Ruby 中执行此操作要容易得多(在我的 Mac 上)。这里有两个例子:
# 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
【讨论】:
【参考方案17】:使用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 使用前面的过滤器,它会找到从 fghfilea
到 jklfilea
的映射,然后将其应用于所有过滤的文件。
其他单线解决方案
其他等效的执行方式(每行都是等效的),因此您可以选择自己喜欢的执行方式。
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 生成功能。
【讨论】:
【参考方案18】:使用mmv:
mmv "fgh*" "jkl#1"
【讨论】:
哇,这是解决问题的绝佳而简单的方法!很高兴被介绍给 mmv,谢谢! 谢谢!!!以前从未听说过mmv。刚刚安装并开始使用它 - 简单、强大。 如果你想批量重命名递归使用;
和#1
。例如:mmv ";fgh*" "#1jkl#2"
非常优雅的解决方案!
正是我需要的。使用 '' 捕获组并使用 #1,#2,... 回调它们 EX: mmv "my show ep 1080p.*" "my.show.#1.#2" = my .show.001.avi【参考方案19】:
这是一种使用命令行 Groovy 的方法:
groovy -e 'new File(".").eachFileMatch(~/fgh.*/) it.renameTo(it.name.replaceFirst("fgh", "jkl"))'
【讨论】:
【参考方案20】:要安装 Perl rename 脚本:
sudo cpan install File::Rename
Stephan202 的回答中的 cmets 中提到了 two renames。 基于 Debian 的发行版具有 Perl rename。 Redhat/rpm 发行版有C rename。 OS X 默认没有安装(至少在 10.8 中),Windows/Cygwin 也没有。
【讨论】:
【参考方案21】:有很多方法可以做到这一点(并非所有这些都适用于所有 unixy 系统):
ls | cut -c4- | xargs -I§ mv fgh§ jkl§
§ 可以用任何你觉得方便的东西代替。您也可以使用 find -exec
来执行此操作,但在许多系统上的行为略有不同,因此我通常会避免这样做
for f in fgh*; do mv "$f" "$f/fgh/jkl";done
正如他们所说的那样粗鲁但有效
rename 's/^fgh/jkl/' fgh*
非常漂亮,但重命名在 BSD 上不存在,这是最常见的 unix 系统 afaik。
rename fgh jkl fgh*
ls | perl -ne 'chomp; next unless -e; $o = $_; s/fgh/jkl/; next if -e; rename $o, $_';
如果你坚持使用 Perl,但你的系统上没有重命名,你可以使用这个怪物。
其中一些有点令人费解,而且列表还远未完成,但您会在此处找到几乎所有 unix 系统所需的内容。
【讨论】:
我喜欢这种怪物! 是的,我仍然有 perl 的弱点 :) 请注意ls
output should not be parsed and/or piped
如果你的文件名中有空格,这里的 Perl 怪物恰好可以完美地工作。【参考方案22】:
rename 可能不会出现在每个系统中。所以如果你没有它,请使用外壳 这个例子在 bash shell 中
for f in fgh*; do mv "$f" "$f/fgh/xxx";done
【讨论】:
在这个解决方案中,sed
命令不是必需的。这个比@nik 的回答简单。
xxx 代表替换?如果是原始海报,那将是“jkl”
最干净的解决方案。
也许更强调指出,这是一个 Bash 扩展,在 POSIX sh
或其他 shell 中不存在。
甜蜜——在 Unix 世界中有这么多不起眼的角落——以前从未见过。【参考方案23】:
rename fgh jkl fgh*
【讨论】:
在我的机器上,这会产生错误'Bareword "fgh" not allowed while "strict subs" in use at (eval 1) line 1.' @Stephan202,你的机器是什么? Ubuntu 8.10 (perl v5.10.0 / 2009-06-26) @Stephan202 我也有同样的问题,你解决了吗? (7 年后) @whossname:对不起,真的不记得了。 (由于假期,回复缓慢。)以上是关于在 Unix 中根据模式重命名多个文件的主要内容,如果未能解决你的问题,请参考以下文章