【问题标题】:Trying to rename certain file types within recursive directories尝试重命名递归目录中的某些文件类型
【发布时间】:2017-01-05 19:06:28
【问题描述】:

我在这样的目录结构中有一堆文件:

Dir
    SubDir
            File
            File
    Subdir
            SubDir
                      File
            File
    File

抱歉,格式混乱,但您可以看到所有不同目录级别的文件。所有这些文件名都有一个由 7 个数字组成的字符串,如下所示:1234567_filename.ext。我正在尝试删除文件名开头的数字和下划线。

现在我正在使用 bash 并使用这个 oneliner 使用 mv 和 cut 重命名文件:

for i in *; do mv "$i" "$(echo $i | cut -d_ -f2-10)"; done

这是在我 CD 进入目录时运行的。我很想找到一种方法来递归地执行此操作,以便它只重命名文件,而不是文件夹。我还在 shell 中使用了一个 foreach 循环,在 bash 之外的目录中有一堆包含文件的文件夹,并且没有其他子目录:

foreach$ set p=`echo $f | cut -d/ -f1`
foreach$ set n=`echo $f | cut -d/ -f2 | cut -d_ -f2-10`
foreach$ mv $f $p/$n
foreach$ end

但这只有在文件夹中没有其他子目录时才有效。

我可以使用循环或单行器来重命名目录中的所有文件吗?我什至尝试使用find,但不知道如何将cut 合并到代码中。

非常感谢任何帮助。

【问题讨论】:

  • 您要删除号码和_ 吗?
  • 拍摄,是的,抱歉,正要编辑帖子以澄清这一点。
  • set不用于设置普通变量,end不是bash关键字。
  • foreach 循环仅在常规 shell 中在 bash 之外使用。
  • 对你拥有的东西做一个小调整怎么样:for i in find .;做 mv "$i" "$(echo $i | cut -d_ -f2-10)";完毕。基本上,将您的 * 替换为 find . 。呃,不知道如何打印反引号,只是添加这个作为答案......

标签: bash macos directory sh batch-rename


【解决方案1】:

使用 Perl 的重命名(独立命令):

shopt -s globstar
rename -n 's|/[0-9]{7}_([^/]+$)|/$1|' **/*

如果一切正常,请删除 -n

globstar: 如果设置,路径名扩展上下文中使用的模式 ** 将 匹配所有文件和零个或多个目录和子目录。如果 模式后跟 /,只有目录和子目录 匹配。

【讨论】:

  • @Inian:谢谢。我已经更新了我的答案并修复了一个错误。防止重命名目录变得更加复杂。
  • 我试过这个,它抛出了一个错误,说 -n 选项对rename 无效。我在 mac osx 终端外壳中使用 bash 4.2。也许它不兼容。我应该提供更多信息。但是感谢您的所有努力!
  • @VanCityGuy:我假设您没有使用 Perl 的重命名实现 This 可能会有所帮助。
【解决方案2】:

bash 确实提供了函数,这些函数可以是递归的,但你不需要递归函数来完成这项工作。您只需要枚举树中的所有文件。 find 命令可以做到这一点,但是打开 bashglobstar 选项并使用 shell glob 来做到这一点更安全:

#!/bin/bash

shopt -s globstar

# enumerate all the files in the tree rooted at the current working directory
for f in **; do
    # ignore directories
    test -d "$f" && continue

    # separate the base file name from the path
    name=$(basename "$f")
    dir=$(dirname "$f")

    # perform the rename, using a pattern substitution on the name part
    mv "$f" "${dir}/${name/#???????_/}"
done

请注意,这并不能验证文件名是否与您在执行重命名之前指定的模式实际匹配;我相信你的话,他们会这样做。如果需要这样的检查,那么当然可以添加。

【讨论】:

  • 这看起来像是赢家。感谢大家的帮助,但这个对我来说效果最好。如果我想修改它以从所有完整文件路径中删除空格并替换为下划线,我会这样做:mv "$f" "${dir/ /_/}/${name/ /_/}"
  • 这看起来像是我的答案底部发布的解决方案的重量级版本。当你有参数扩展时,你真的需要使用外部工具basenamedirname吗?排除像abcdefg_abcd.ext 这样的文件名怎么样,即没有数字?
  • @VanCityGuy - 不,你可能不会那样做......如果你尝试mv "foo bar/baz" "foo_bar/baz",并且目标目录不存在,mv 将会失败。
【解决方案3】:

对你已有的东西做这个小调整怎么样:

for i in `find . -type f`; do mv "$i" "$(echo $i | cut -d_ -f2-10)"; done

基本上只是将 * 与 `find 交换。 -type f`

【讨论】:

    【解决方案4】:

    应该可以使用find...

    find -E . -type f \
      -regex '.*/[0-9]{7}_.*\.txt' \
      -exec sh -c 'f="${0#*/}"; mv -v "$0" "${0%/*}/${f#*_}"' {} \;
    

    您的find 选项可能不同——我在FreeBSD 中这样做。这里的想法是:

    • -E 指示 find 使用扩展正则表达式。
    • -type f 导致只能找到普通文件(而不是目录或符号链接)。
    • -regex ... 匹配您要查找的文件。如果需要,您可以更具体地说明。
    • exec ... \; 运行命令,使用 {}(我们找到的文件)作为参数。

    我们正在运行的命令首先使用参数扩展来获取目标目录,然后再剥离文件名。注意临时变量$f,它用于解决额外下划线成为文件名一部分的可能性。

    请注意,这不是bash 命令,尽管您当然可以从 bash shell 运行它。如果您想要一个不需要使用像 find 这样的外部工具的 bash 解决方案,您可以执行以下操作:

    $ shopt -s extglob   # use extended glob format
    $ shopt -s globstar  # recurse using "**"
    $ for f in **/+([0-9])_*.txt; do f="./$f"; echo mv "$f" "${f%/*}/${f##*_}"; done
    

    这使用与 find 解决方案相同的逻辑,但使用 bash v4 extglob 提供更好的文件名匹配和 globstar 递归子目录。

    希望这些帮助。

    【讨论】:

      最近更新 更多