我试图将所有扩展名都转换为小写,而不管它是什么。到目前为止,从我所看到的,你需要指定要转换为小写的文件扩展名。然而,我只想在名称中最后一个点.
之后将其余所有内容转换为小写。
如何在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
对于包含换行符的文件名,此方法仍然无法正常工作。要解决此问题,您需要使用支持 -print0
(如 GNU 的 find
)和 Bash(这样 read
就支持 -d
分隔符开关)的 find
命令:
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
进程并不高效。这是正确的,此时使用之前的循环版本会更好。
brew install gnu-sed
安装gnu-sed,并用gsed替换sed来运行您的shell命令。 - duozmo$0
而是想用$1
(也许是因为你正在复制和粘贴其他已经存在的一行代码或函数)。在这种情况下,可以使用如下命令:-exec sh -c '... stuff with "$1" ...' _ {} \;
。这里的_
充当了一个虚拟参数的角色(它可以是任何其他字符),而"$1"
则是文件名的正确且安全的引用扩展。下一个技巧:当使用-exec ... +
时,请使用以下命令:-exec sh -c 'for i; do ... stuff with $i ...; done' _ {} +
。在这里,你确实需要添加一个虚拟参数。 - gniourf_gniourf我用这个命令成功了。
rename JPG jpg *.JPG
其中rename
是一个命令,它告诉shell将当前文件夹中的每个JPG
扩展名的文件重命名为jpg
。
如果您使用此方法出现“在(eval 1) line 1处使用严格的subs时不允许裸字“JPG””的错误,请尝试以下方法:
rename 's/\.JPG$/.jpg/' *.JPG
brew install rename
命令可以安装 rename 工具,但由于 Mac 的文件系统不区分大小写,你需要先将文件名重命名为 jpg_
,再将其从 jpg_
重命名回 jpg
。 - Qix - MONICA WAS MISTREATED这个回答更短,但更为通用,结合了其他人的答案:
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
表示点前缀(\.
)后跟第一个组($1
)的小写字母 (\L
)[^.]+
)'
而不是双引号 "
来包含正则表达式,以避免shell扩展find . -type f -print0 | xargs -0 -I {} rename 's/\.([^.]+)$/.\L$1/gi' "{}"
- scrat.squirrel嗯,你可以将这个片段作为任何你需要的替代方案的核心:
#!/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
如果您需要更具体的答案,您需要提供更详细的信息...
tr '[:ucase:]' '[:lcase:]'
也可以,但使用任何类型的命令替换都会启动至少一个新进程来执行大小写转换... - thkalamv xx.YY xx.yy
会生成mv:'xx.YY'和'xx.yy'是同一个文件
。(您必须分两步完成,例如:mv xx.YY xx.YY.$$; mv xx.YY.$$ xx.yy
。)但是,由于标签是“linux”,因此此解决方案是可行的。 - Jonathan Leffler-r
开关来修复read
。对于前者,您需要一个支持-print0
的find
,使用它,并像这样修改您的read
语句:IFS= read -r -d '' f
。 - gniourf_gniourf # 提取扩展名
x="${f##*.}"
# 将扩展名转换为小写
# 适用于bash版本> 2
typeset -l l=$x
if [[ "$x" != "$l" ]]; then
mv "$f" "$b.$l"
fi
else
continue
fi
done
` - Frederic Henrifor 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
.mp3
扩展名的文件,那么这将会遇到问题——mv
会指示错误,因为两个文件是相同的。 - Jonathan Leffler递归地为所有一个好的解决方案:
find -name '*.JPG' | sed 's/\(.*\)\.JPG/mv "\1.JPG" "\1.jpg"/' |sh
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
命令更易于使用和记忆。
mmv
是一个很棒的工具,只要知道它会触及到与源模式匹配的所有文件的 __ctimes__,也就是使用 -n 和 -v 选项列出的文件,无论它们是否被重命名。 - undefined我一开始想找一个简单的方法来做这件事(而不必考虑它),但最终我还是仔细思考了一下,并想出了这个方法(诚然,比原帖晚得多)。
find . -name \\*.JPG -print -exec rename s/.JPG/.jpg/ {} \\;
-n
选项进行“重命名”。#!/bin/bash
# find files with at least one uppercase character in an extension in the input directory recursively and optionally convert extensions to lowercase
# usage:
# find_file_extensions_with_uppercase_chars dir [convert]
dir=$1
[[ -z $1 ]] && dir=.
shopt -s nullglob dotglob globstar extglob
for file in "$dir"/**/*; do
if [[ ! -d $file ]]; then
filename=${file##*/}
if [[ $filename =~ \. ]]; then
ext="${filename#*.}"
if [[ $ext =~ [[:upper:]] ]]; then
file="${file//+(\/)/\/}"
echo $file
if [[ -n $2 ]]; then
ext_length=${#ext}
conv_file=${file::((-ext_length))}${ext,,}
mv -iv "$file" "$conv_file"
fi
fi
fi
fi
done
ext="${filename#*.}"
改为ext="${filename##*.}"
。