将所有文件扩展名转换为小写

51

我试图将所有扩展名都转换为小写,而不管它是什么。到目前为止,从我所看到的,你需要指定要转换为小写的文件扩展名。然而,我只想在名称中最后一个点.之后将其余所有内容转换为小写。

如何在bash中完成这个操作?


提问者最近又有一个关于Bash脚本重命名文件的问题。因此,他可能正在询问如何编写Bash脚本来更改每个文件的名称(在他的主目录中?在目录中和下面的目录中?只是在一个目录中?),以将文件名扩展名中的每个大写字母更改为小写字母,除了第一个字母。例如,“PICTURE.JPG”将被重命名为“PICTURE.Jpg”。 - Eric Postpischil
我有以testing.mP3和testing.Mp3结尾的文件 - 我想将它们全部重命名为*.mp3。 - thevoipman
12个回答

88

解决方案

您可以用一行代码解决此任务:

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 进程并不高效。这是正确的,此时使用之前的循环版本会更好。


8
Macs自带BSD的sed,而不是GNU的sed,不幸的是只有后者支持小写\L序列。有Homebrew的Mac用户可以通过brew install gnu-sed安装gnu-sed,并用gsed替换sed来运行您的shell命令。 - duozmo
1
@gniourf_gniourf:非常好的技巧!(我指的是"$0"和{}的技巧。)谢谢你分享。真的很酷。 - Igor Chubin
另外还有两个类似的技巧:有时候你不想使用$0而是想用$1(也许是因为你正在复制和粘贴其他已经存在的一行代码或函数)。在这种情况下,可以使用如下命令:-exec sh -c '... stuff with "$1" ...' _ {} \;。这里的_充当了一个虚拟参数的角色(它可以是任何其他字符),而"$1"则是文件名的正确且安全的引用扩展。下一个技巧:当使用-exec ... +时,请使用以下命令:-exec sh -c 'for i; do ... stuff with $i ...; done' _ {} +。在这里,你确实需要添加一个虚拟参数。 - gniourf_gniourf
同时,明确哪个版本是POSIX(即相对可移植的),以及使用GNU-isms和bashisms的版本将会很棒...嗯,还有一些工作要做,才能得出一个完美的答案,这将是最终答案。 - gniourf_gniourf
1
这个问题并没有要求,但在Mac上你可以将这个版本包装在Automator中,并将其保存为服务,这样你就可以右键单击文件并将它们的扩展名转换为小写。 - duozmo
显示剩余13条评论

62

我用这个命令成功了。

rename JPG jpg *.JPG

其中rename是一个命令,它告诉shell将当前文件夹中的每个JPG扩展名的文件重命名为jpg

如果您使用此方法出现“在(eval 1) line 1处使用严格的subs时不允许裸字“JPG””的错误,请尝试以下方法:

rename 's/\.JPG$/.jpg/' *.JPG

10
在 Mac 上,brew install rename 命令可以安装 rename 工具,但由于 Mac 的文件系统不区分大小写,你需要先将文件名重命名为 jpg_,再将其从 jpg_ 重命名回 jpg - Qix - MONICA WAS MISTREATED
3
我喜欢这个答案。只需进行简单的正则表达式匹配和替换,这对使用rename命令进行其他情况非常灵活。在执行操作前使用“-n”开关来进行“干运行”,以防您犯错误。 - Andy H
“rename” 是 Debian 系统上的软件包名称。 - Imran-UK

17

这个回答更短,但更为通用,结合了其他人的答案:

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扩展

3
我认为这是最好的答案,因为它简洁优雅,实际上回答了问题(相比其他得票更高的答案)!可能来晚了一些... - Exocom
1
在我看来,最好的答案是,这里有一个一行代码将所有文件的扩展名改为小写的方法:find . -type f -print0 | xargs -0 -I {} rename 's/\.([^.]+)$/.\L$1/gi' "{}" - scrat.squirrel

11

嗯,你可以将这个片段作为任何你需要的替代方案的核心:

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

如果您需要更具体的答案,您需要提供更详细的信息...


1
我经常在脚本中使用dd进行大小写转换。lower=$( echo "FOO" | dd conv=lcase ) - Adam
2
@Adam:tr '[:ucase:]' '[:lcase:]' 也可以,但使用任何类型的命令替换都会启动至少一个新进程来执行大小写转换... - thkala
1
请注意,在不区分大小写的文件系统上(例如默认情况下在Mac OS X上找到的文件系统),您不能简单地像这样重命名文件:尝试mv xx.YY xx.yy会生成mv:'xx.YY'和'xx.yy'是同一个文件。(您必须分两步完成,例如:mv xx.YY xx.YY.$$; mv xx.YY.$$ xx.yy。)但是,由于标签是“linux”,因此此解决方案是可行的。 - Jonathan Leffler
对于包含换行符或反斜杠的文件名,此代码会出现错误。后者可以通过使用-r开关来修复read。对于前者,您需要一个支持-print0find,使用它,并像这样修改您的read语句:IFS= read -r -d '' f - gniourf_gniourf
如果您使用较旧版本的bash,可以使用typeset命令(适用于bash版本2及更早版本)。脚本将是`#!/bin/bash

lowerext.sh

while read f; do if [[ "$f" = . ]]; then # 提取基本名称 b="${f%.*}" # 提取扩展名 x="${f##*.}" # 将扩展名转换为小写 # 适用于bash版本> 2 typeset -l l=$x if [[ "$x" != "$l" ]]; then mv "$f" "$b.$l" fi else continue fidone `
- Frederic Henri
显示剩余2条评论

8
如果您正在使用ZSH:
zmv '(*).(*)' '$1.$2:l'

如果你遇到了“zsh: command not found: zmv”的情况,那么只需运行以下命令即可:
autoload -U zmv

然后再试一次。

感谢这篇原始文章提供的zmv技巧和ZSH文档提供的大小写替换语法。


1
另外,zmv -v '(*)' '${(L)f:gs/ /_/}'将会替换所有文件名中的空格为下划线,并将名称转换为小写(在屏幕上显示更改)。 - user4104817
在 macOS 上对我来说非常准确 :) - owenmelbz

5
这将为您的“.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

如果您有任何已经具有正确的.mp3扩展名的文件,那么这将会遇到问题——mv会指示错误,因为两个文件是相同的。 - Jonathan Leffler
@Jonathan Leffler:我看到了,但认为它会起作用 - 现在我知道它不行了 - 谢谢。我会在我的答案中修复它。 - tzelleke

2

递归地为所有一个好的解决方案:

find -name '*.JPG' | sed 's/\(.*\)\.JPG/mv "\1.JPG" "\1.jpg"/' |sh

以上递归地将扩展名为“JPG”的文件重命名为扩展名为“jpg”的文件。

这非常糟糕和损坏。请勿使用。 - gniourf_gniourf

1
如果您已经安装了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命令更易于使用和记忆。


1
mmv 是一个很棒的工具,只要知道它会触及到与源模式匹配的所有文件的 __ctimes__,也就是使用 -n 和 -v 选项列出的文件,无论它们是否被重命名。 - undefined

1

我一开始想找一个简单的方法来做这件事(而不必考虑它),但最终我还是仔细思考了一下,并想出了这个方法(诚然,比原帖晚得多)。

find . -name \\*.JPG -print -exec rename s/.JPG/.jpg/ {} \\;

我在大约六万个文件上运行它,它可以正常工作,但是,当然,如果您想先测试一下,您可以使用-n选项进行“重命名”。

找到:缺少“-exec”的参数。 - Al Lelopath

0
我的一个小而原始的脚本,可以递归地将文件扩展名转换为小写:
#!/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##*.}"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接