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

时间:2012-08-05 17:11:57

标签: linux bash file-extension

我正试图降低所有扩展程序的大小写,无论它是什么。到目前为止,根据我所看到的,您必须指定要转换为小写的文件扩展名。但是,我只想在名称中第一个最后一个点.之后小写一切。

我如何在bash中执行此操作?

12 个答案:

答案 0 :(得分:62)

<强>解决方案

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

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 go(回到最初的解决方案,其中包含一些非常简单的修复程序):

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

答案 1 :(得分:47)

我用这个命令取得了成功。

rename JPG jpg *.JPG

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

如果您看到Bareword“JPG”不允许使用“eval 1”第1行使用“strict subs”时尝试:

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

答案 2 :(得分:10)

好吧,您可以使用此代码段作为您需要的替代方案的核心:

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

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

答案 3 :(得分:9)

这个更短但更通用,与其他答案相结合:

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 st 组的小写(\L)($1
  • 在这种情况下,第一组是扩展名([^.]+
  • 您最好使用单引号'而不是双引号"来包装正则表达式以避免shell扩展

答案 4 :(得分:3)

这将为你的'.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

答案 5 :(得分: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内容更容易使用和记忆。

答案 6 :(得分:1)

递归地获取所有一个精细解决方案:

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

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

答案 7 :(得分:1)

如果您使用的是ZSH:

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

如果你得到zsh: command not found: zmv,那么只需运行:

autoload -U zmv

然后再试一次。

感谢这个original article关于zmv的提示和ZSH documentation的小写/大写替换语法。

答案 8 :(得分:0)

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

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

使用-n选项来测试将要更改的内容,然后当您对结果使用时感到满意

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

答案 9 :(得分:0)

如果已安装JDK,则可以简单地编译为以下内容:

import java.io.File;

public class FileRename {
    public static void main(String[] args) {
        long numFilesChanged = 0;
        long numFilesNotChanged = 0;

        try {
            final File directory = new File(".");
            final File[] listOfFiles = directory.listFiles();

            for (int i = 0; i < listOfFiles.length; i++) {
                if (!listOfFiles[i].isFile()) {
                    continue;
                }

                final File fileToRename = listOfFiles[i];

                final String filename = fileToRename.getName();
                final String woExtension = filename.substring(0, filename.lastIndexOf('.'));
                final String extension = filename.substring(filename.lastIndexOf('.'));

                if (extension.toLowerCase().equals(extension)) {
                    numFilesNotChanged++;
                } else {
                    numFilesChanged++;
                    fileToRename.renameTo(new File(woExtension + extension.toLowerCase()));
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("Changed: " + numFilesChanged);
        System.out.println("Not changed:" + numFilesNotChanged);
    }
}

然后运行。

如果您没有安装JDK但安装了Java,请使用在线Java编译器(例如:https://www.compilejava.net/)对其进行编译,然后将生成的.class文件放入要重命名文件并执行的文件夹中: java FileRename

如果您不熟悉Java或需要帮助进行编译和/或运行,请随时与我联系。

答案 10 :(得分:0)

我一直在寻找一种简单的方法(无需考虑),但最终我终于想出了办法,并提出了这个方法(当然,是在原始帖子之后)

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

我在大约6万个文件上运行了该文件,并且运行良好,但是,如果要先对其进行测试,则可以使用-n选项“重命名”。

答案 11 :(得分:-1)

所以,这些看起来像线路噪音的解决方案都很好,但是这很容易从python REPL做起(我知道OP要求bash,但python安装在很多现在都有bash的系统上...):

import os
files = os.listdir('.')
for f in files:
    path, ext = os.path.splitext(f)
    if ext.isupper():
        os.rename(f, path + ext.lower())