48

无论扩展名是什么,我都在尝试小写所有扩展名。到目前为止,据我所见,您必须指定要转换为小写的文件扩展名。但是,我只想将名称中第一个最后一个点之后的所有内容都小写.

我怎样才能做到这一点bash

4

11 回答 11

80

解决方案

您可以在一行中解决任务:

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您可能会争辩说,为找到的每个文件生成一个进程效率不高。那是正确的,以前的循环版本会更好。

于 2012-08-06T08:29:25.000 回答
60

我用这个命令成功了。

rename JPG jpg *.JPG

whererename是一个命令,它告诉 shell 将当前文件夹中的每个 to 重命名JPGjpg所有文件名都具有扩展名JPG.

如果您在 (eval 1) 第 1 行使用此方法使用“strict subs”时看到 Bareword “JPG” not allowed,请尝试:

rename 's/\.JPG$/.jpg/' *.JPG
于 2014-01-07T04:45:25.417 回答
14

结合其他人的答案,这更短但更笼统:

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( )
  • 在这种情况下,第一组是扩展名 ( [^.]+)
  • 您最好使用单引号'而不是双引号"来包装正则表达式以避免外壳扩展
于 2015-09-23T01:48:45.670 回答
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

如果您需要比这更具体的答案,则必须更具体...

于 2012-08-05T18:09:31.860 回答
6

如果您使用的是 ZSH:

zmv '(*).(*)' '$1.$2:l'

如果你得到zsh: command not found: zmv然后简单地运行:

autoload -U zmv

然后再试一次。

感谢这篇关于 zmv 的原始文章以及关于小写/大写替换语法的ZSH 文档。

于 2017-06-20T02:49:58.033 回答
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
于 2012-08-05T18:22:27.493 回答
2

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

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

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

于 2015-01-30T10:46:26.597 回答
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东西更容易使用和记住。

于 2013-01-10T15:19:03.140 回答
0

如果您只对某些文件扩展名感兴趣,例如将所有大写“JPG”扩展名转换为小写“jpg”,您可以像这样使用命令行实用程序重命名。将 CD 放入您要更改的目录。然后

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

使用 -n 选项来测试将要更改的内容,然后当您对结果满意时使用,而不是这样

rename  's/\.JPG$/\.jpg/' *
于 2015-08-16T16:47:40.443 回答
0

我一直在寻找一种简单的方法来做到这一点(无需考虑),但最终我想通了并想出了这个(诚然,在原始帖子之后)

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

我在大约 60,000 个文件上运行它,它运行良好,但当然,-n如果你想先测试它,你可以使用“重命名”选项。

于 2019-11-14T17:25:44.063 回答
-1

所以,这些看起来像线路噪声的解决方案都很好,但这很容易从 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())
于 2013-11-10T06:26:54.517 回答