使用正则表达式重命名 linux 中的文件
Rename files in linux with regex
这其实不是问题,是我自己解决的。但我想post在这里提出我的解决方案,以节省处于相同情况下的其他人的时间和精力。
所以我遇到了必须重命名很多 (+3000) 匹配特定模式的情况。在我的例子中,文件是 syncthing 的自动备份,因此文件将重命名为:
foo.bar -> foo~20150221-1330.bar
在通过论坛和手册页进行大量搜索后,我创建了以下单行代码,它使用 find、sed
、xargs
和 mv
命令恢复了原始文件名在 linux:
find . -type f | sed -e 'p;s/\(.*\)~20[0-9]\{6\}-[0-9]\{6\}\(.*\)//' | xargs -n2 -d'\n' mv
如果您愿意,可以将 sed
部分替换为您自己的模式。该命令可以顺便处理空格(感谢 xargs
中的 -d'\n'
标志),但不能处理换行符。
我希望你们中的一些人发现这个命令有用。
好的,我将提供有关每个命令的作用的更多信息:
find
:给出当前目录下的所有常规文件(不是目录)
sed
:p
将打印来自标准输入的每一行,s/regex/regex/
将打印相同的行,但被替换。所以你得到每个文件后跟固定文件名:
./foo/bar~20150221-172703.txt
./foo/bar.txt`
xargs
: -n2
将采用两行并将它们作为参数发送到 mv
,-d'\n' 将修复文件夹名称中的空格问题(分隔符是设置为换行符而不是空格)
你的答案很好,但是如果你还想解决也包含换行符的文件名,如果你不想使用 sed
并且你想确保只有带有 [=36 的文件=]basename 匹配这个模式被考虑在内(你的方法失败了,例如,dir~20150221-172703/file
,你的方法让 mv
抱怨很多关于 file
和 file
当文件名与你的模式不匹配时是同一个文件)你需要稍微不同地进行。
一种可能性,如果您的 find
支持 -print0
选项(GNU find
支持):让 find
吐出所有文件名,并使用 while
循环,其中 Bash(不是 sed
)将执行替换。像这样:
find . -type f -print0 | while IFS= read -r -d '' file; do
dirname=${file%/*}
basename=${file##*/}
# Perform the substitution only on basename
# Since you like regex, you can use them
if [[ $basename =~ ^(.*)~20[0-9]{6}-[0-9]{6}(.*)$ ]]; then
new_basename=${BASH_REMATCH[1]}${BASH_REMATCH[2]}
echo mv "$dirname/$basename" "$dirname/$new_basename"
fi
done
您还可以通过使用 -regex
过滤器(不是 POSIX,但 GNU find
支持它)来确保 find
仅吐出匹配的文件:
find . -regextype posix-basic -regex '.*~20[0-9]\{6\}-[0-9]\{6\}[^/]*' -type f
不确定这是否完全回答了您的问题,但至少它修复了您的方法存在的缺陷。
没有 GNU 的好东西,就很难有一些健壮和干净的东西...
您可以使用 rnm:
rnm -rs '/(.*)~20[0-9]{6}-[0-9]{6}(.*)//' -fo -dp -1 *
-fo
: 仅文件模式
-dp
:目录深度(-1 表示无限深度,即转到所有子目录)。
不过您可以完全使用自己的正则表达式模式,在这种情况下,您必须将正则表达式模式更改为基本 (BRE):
rnm --regex basic -rs '/\(.*\)~20[0-9]\{6\}-[0-9]\{6\}\(.*\)//' -fo -dp -1 *
注:
- 唯一无效字符是空字符和路径定界符 (
/
)。
- 默认正则表达式模式为 javascript。
这其实不是问题,是我自己解决的。但我想post在这里提出我的解决方案,以节省处于相同情况下的其他人的时间和精力。
所以我遇到了必须重命名很多 (+3000) 匹配特定模式的情况。在我的例子中,文件是 syncthing 的自动备份,因此文件将重命名为:
foo.bar -> foo~20150221-1330.bar
在通过论坛和手册页进行大量搜索后,我创建了以下单行代码,它使用 find、sed
、xargs
和 mv
命令恢复了原始文件名在 linux:
find . -type f | sed -e 'p;s/\(.*\)~20[0-9]\{6\}-[0-9]\{6\}\(.*\)//' | xargs -n2 -d'\n' mv
如果您愿意,可以将 sed
部分替换为您自己的模式。该命令可以顺便处理空格(感谢 xargs
中的 -d'\n'
标志),但不能处理换行符。
我希望你们中的一些人发现这个命令有用。
好的,我将提供有关每个命令的作用的更多信息:
find
:给出当前目录下的所有常规文件(不是目录)sed
:p
将打印来自标准输入的每一行,s/regex/regex/
将打印相同的行,但被替换。所以你得到每个文件后跟固定文件名:./foo/bar~20150221-172703.txt ./foo/bar.txt`
xargs
:-n2
将采用两行并将它们作为参数发送到mv
,-d'\n' 将修复文件夹名称中的空格问题(分隔符是设置为换行符而不是空格)
你的答案很好,但是如果你还想解决也包含换行符的文件名,如果你不想使用 sed
并且你想确保只有带有 [=36 的文件=]basename 匹配这个模式被考虑在内(你的方法失败了,例如,dir~20150221-172703/file
,你的方法让 mv
抱怨很多关于 file
和 file
当文件名与你的模式不匹配时是同一个文件)你需要稍微不同地进行。
一种可能性,如果您的 find
支持 -print0
选项(GNU find
支持):让 find
吐出所有文件名,并使用 while
循环,其中 Bash(不是 sed
)将执行替换。像这样:
find . -type f -print0 | while IFS= read -r -d '' file; do
dirname=${file%/*}
basename=${file##*/}
# Perform the substitution only on basename
# Since you like regex, you can use them
if [[ $basename =~ ^(.*)~20[0-9]{6}-[0-9]{6}(.*)$ ]]; then
new_basename=${BASH_REMATCH[1]}${BASH_REMATCH[2]}
echo mv "$dirname/$basename" "$dirname/$new_basename"
fi
done
您还可以通过使用 -regex
过滤器(不是 POSIX,但 GNU find
支持它)来确保 find
仅吐出匹配的文件:
find . -regextype posix-basic -regex '.*~20[0-9]\{6\}-[0-9]\{6\}[^/]*' -type f
不确定这是否完全回答了您的问题,但至少它修复了您的方法存在的缺陷。
没有 GNU 的好东西,就很难有一些健壮和干净的东西...
您可以使用 rnm:
rnm -rs '/(.*)~20[0-9]{6}-[0-9]{6}(.*)//' -fo -dp -1 *
-fo
: 仅文件模式-dp
:目录深度(-1 表示无限深度,即转到所有子目录)。
不过您可以完全使用自己的正则表达式模式,在这种情况下,您必须将正则表达式模式更改为基本 (BRE):
rnm --regex basic -rs '/\(.*\)~20[0-9]\{6\}-[0-9]\{6\}\(.*\)//' -fo -dp -1 *
注:
- 唯一无效字符是空字符和路径定界符 (
/
)。 - 默认正则表达式模式为 javascript。