无论扩展名是什么,我都在尝试小写所有扩展名。到目前为止,据我所见,您必须指定要转换为小写的文件扩展名。但是,我只想将名称中第一个最后一个点之后的所有内容都小写.
。
我怎样才能做到这一点bash
?
无论扩展名是什么,我都在尝试小写所有扩展名。到目前为止,据我所见,您必须指定要转换为小写的文件扩展名。但是,我只想将名称中第一个最后一个点之后的所有内容都小写.
。
我怎样才能做到这一点bash
?
解决方案
您可以在一行中解决任务:
find . -name '*.*' -exec sh -c '
a=$(echo "$0" | sed -r "s/([^.]*)\$/\L\1/");
[ "$a" != "$0" ] && mv "$0" "$a" ' {} \;
注意:这将破坏包含换行符的文件名。但现在请忍受我。
使用示例
$ mkdir C; touch 1.TXT a.TXT B.TXT C/D.TXT
$ find .
.
./C
./C/D.TXT
./1.TXT
./a.TXT
./B.TXT
$ find . -name '*.*' -exec sh -c 'a=$(echo "$0" | sed -r "s/([^.]*)\$/\L\1/"); [ "$a" != "$0" ] && mv "$0" "$a" ' {} \;
$ find .
.
./C
./C/D.txt
./a.txt
./B.txt
./1.txt
解释
您可以在当前目录 ( .
) 中找到.
名称中包含句点 ( ) 的所有文件,-name '*.*'
并为每个文件运行以下命令:
a=$(echo "$0" | sed -r "s/([^.]*)\$/\L\1/");
[ "$a" != "$0" ] && mv "{}" "$a"
该命令意味着:尝试将文件扩展名转换为小写(即sed
):
$ echo 1.txt | sed -r "s/([^.]*)\$/\L\1/"
1.txt
$ echo 2.TXT | sed -r "s/([^.]*)\$/\L\1/"
2.txt
并将结果保存到a
变量中。
如果有更改[ "$a" != "$0" ]
,请重命名文件mv "$0" "$a"
。
正在处理的文件的名称 ( {}
) 作为其附加参数传递给sh -c
它,它在命令行中显示为$0
. 它使脚本安全,因为在这种情况下,shell 将 {} 作为数据,而不是作为代码部分,就像在命令行中直接指定时那样。(感谢@gniourf_gniourf指出这个非常重要的问题)。
如您所见,如果您{}
直接在脚本中使用,则可能在文件名中有一些 shell 注入,例如:
; rm -rf * ;
在这种情况下,注入将被 shell 视为代码的一部分,它们将被执行。
版本
更清晰但更长一点的脚本版本:
find . -name '*.*' | while IFS= read -r f
do
a=$(echo "$f" | sed -r "s/([^.]*)\$/\L\1/");
[ "$a" != "$f" ] && mv "$f" "$a"
done
对于包含换行符的文件名,这仍然会中断。要解决此问题,您需要一个find
支持-print0
(如 GNU find
)和 Bash (以便read
支持-d
分隔符开关):
find . -name '*.*' -print0 | while IFS= read -r -d '' f
do
a=$(echo "$f" | sed -r "s/([^.]*)\$/\L\1/");
[ "$a" != "$f" ] && mv "$f" "$a"
done
对于包含尾随换行符的文件,这仍然会中断(因为它们将a=$(...)
被子shell吸收。如果你真的想要一个万无一失的方法(你应该!),使用支持,,
参数扩展的最新版本的 Bash (Bash≥4.0) 这里是最终解决方案:
find . -name '*.*' -print0 | while IFS= read -r -d '' f
do
base=${f%.*}
ext=${f##*.}
a=$base.${ext,,}
[ "$a" != "$f" ] && mv -- "$f" "$a"
done
回到原来的解决方案
或者find
一口气(回到原始解决方案,并进行一些修复,使其真正万无一失):
find . -name '*.*' -type f -exec bash -c 'base=${0%.*} ext=${0##*.} a=$base.${ext,,}; [ "$a" != "$0" ] && mv -- "$0" "$a"' {} \;
我添加了-type f
以便仅重命名常规文件。如果没有这个,如果在文件名之前重命名目录名,您仍然可能会遇到问题。如果您还想重命名目录(以及链接、管道等),您应该使用-depth
:
find . -depth -name '*.*' -type f -exec bash -c 'base=${0%.*} ext=${0##*.} a=$base.${ext,,}; [ "$a" != "$0" ] && mv -- "$0" "$a"' {} \;
以便find
执行深度优先搜索。
bash
您可能会争辩说,为找到的每个文件生成一个进程效率不高。那是正确的,以前的循环版本会更好。
我用这个命令成功了。
rename JPG jpg *.JPG
whererename
是一个命令,它告诉 shell 将当前文件夹中的每个 to 重命名JPG
,jpg
所有文件名都具有扩展名JPG
.
如果您在 (eval 1) 第 1 行使用此方法使用“strict subs”时看到 Bareword “JPG” not allowed,请尝试:
rename 's/\.JPG$/.jpg/' *.JPG
结合其他人的答案,这更短但更笼统:
rename 's/\.([^.]+)$/.\L$1/' *
模拟
对于模拟,使用-n
,即rename -n 's/\.([^.]+)$/.\L$1/' *
。通过这种方式,您可以在执行实际更改之前查看将要更改的内容。示例输出:
Happy.Family.GATHERING.JPG renamed as Happy.Family.GATHERING.jpg
Hero_from_The_Land_Across_the_River.JPG renamed as Hero_from_The_Land_Across_the_River.jpg
rAnD0m.jPg1 renamed as rAnD0m.jpg1
关于语法的简短说明
rename OPTIONS 's/WHAT_TO_FIND_IN_THE_NAME/THE_REPLACEMENT/' FILENAMES
\.([^.]+)$
表示字符串 ( )[^.]
末尾的点 ( )之外的任何序列,点 ( )之后$
\.
.\L$1
表示点 ( \.
) 后跟\L
第一组 ( ) 的小写$1
( )[^.]+
)'
而不是双引号"
来包装正则表达式以避免外壳扩展好吧,您可以将此代码段用作您需要的任何替代方案的核心:
#!/bin/bash
# lowerext.sh
while read f; do
if [[ "$f" = *.* ]]; then
# Extract the basename
b="${f%.*}"
# Extract the extension
x="${f##*.}"
# Convert the extension to lower case
# Note: this only works in recent versions of Bash
l="${x,,}"
if [[ "$x" != "$l" ]]; then
mv "$f" "$b.$l"
fi
else
continue
fi
done
之后,您需要做的就是将需要重命名的文件列表提供给其标准输入。例如对于当前目录和任何子目录下的所有文件:
find -type f | lowerext.sh
一个小优化:
find -type f -name '*.*' | lowerext.sh
如果您需要比这更具体的答案,则必须更具体...
这将为您的 '.mp3' 完成这项工作 - 但仅在工作目录中 - 但是能够使用带有空格的文件名:
for f in *.[mM][pP]3; do mv "$f" "${f%.*}.mp3"; done
更正:
for f in *.[mM][pP]3; do [[ "$f" =~ \.mp3$ ]] || mv "$f" "${f%.*}.mp3"; done
递归地为所有一个好的解决方案:
find -name '*.JPG' | sed 's/\(.*\)\.JPG/mv "\1.JPG" "\1.jpg"/' |sh
以上递归将扩展名为“JPG”的文件重命名为扩展名为“jpg”的文件
如果您安装了mmv
(=移动多个文件)并且您的文件名最多包含一个点,您可以使用
mmv -v "*.*" "#1.#l2"
它没有得到超过一个点(因为匹配的算法在*
中不是贪心的mmv
),但是,它可以()
正确处理'
。例子:
$ mmv -v "*.*" "#1.#l2"
FOO.BAR.MP3 -> FOO.bar.mp3 : done
foo bar 'baz' (CD 1).MP3 -> foo bar 'baz' (CD 1).mp3 : done
不完美,但比所有find/exec/sed
东西更容易使用和记住。
如果您只对某些文件扩展名感兴趣,例如将所有大写“JPG”扩展名转换为小写“jpg”,您可以像这样使用命令行实用程序重命名。将 CD 放入您要更改的目录。然后
rename -n 's/\.JPG$/\.jpg/' *
使用 -n 选项来测试将要更改的内容,然后当您对结果满意时使用,而不是这样
rename 's/\.JPG$/\.jpg/' *
我一直在寻找一种简单的方法来做到这一点(无需考虑),但最终我想通了并想出了这个(诚然,在原始帖子之后)
find . -name \\*.JPG -print -exec rename s/.JPG/.jpg/ {} \\;
我在大约 60,000 个文件上运行它,它运行良好,但当然,-n
如果你想先测试它,你可以使用“重命名”选项。
所以,这些看起来像线路噪声的解决方案都很好,但这很容易从 python REPL 中做到(我知道 OP 要求使用 bash,但是这些天很多系统都安装了 python ...... ):
import os
files = os.listdir('.')
for f in files:
path, ext = os.path.splitext(f)
if ext.isupper():
os.rename(f, path + ext.lower())