【问题标题】:Find and replace filename recursively in a directory在目录中递归查找和替换文件名
【发布时间】:2023-05-18 23:28:01
【问题描述】:

我想把以123_xxx.txt开头的文件夹中的所有文件重命名为xxx.txt

例如,我的目录有:

123_xxx.txt
123_yyy.txt
123_zzz.txt

我想将所有文件重命名为:

xxx.txt
yyy.txt
zzz.txt

我在这个论坛上看到了一些有用的 bash 脚本,但我仍然对如何使用它来满足我的要求感到困惑。

假设我使用:

for file in `find -name '123_*.txt'` ; do mv $file {?.txt} ; done

这是正确的做法吗?

【问题讨论】:

    标签: bash find


    【解决方案1】:

    你可以这样做:

    find . -name '123_*.txt' -type f -exec sh -c '
    for f; do
        mv "$f" "${f%/*}/${f##*/123_}"
    done' sh {} +
    

    没有管道,没有读取,没有机会破坏格式错误的文件名,没有非标准的工具或功能。

    【讨论】:

    • 这是利用parameter expansion 从路径末尾剥离基本名称,然后附加一个新名称,该名称的前缀最多为 123_。要阅读的两个运算符是%##,其余的是简单的通配符。
    【解决方案2】:
    find . -name "123*.txt" -exec rename 's/^123_//' {} ";" 
    

    会做的。不需要 AWK,不需要 for,不需要 xargs,但需要 rename,这是 Perl 库中一个非常有用的命令。它并不总是包含在 Linux 中,但很容易从 repos 中安装。

    【讨论】:

    • 对于那些拥有 perl rename 的人来说,这优于过度杀伤 awk 和循环上面的答案。
    • 你在 -name 前少了一个点 (.),在 mac 上无法运行
    • @ChanLe Gnu 在 Linux 上的 find 不需要目录名来操作,但需要 '.'自动,如果缺少名称。
    • 补充一点,如果您只讨论单个目录中的文件,则不需要使用 find 。最好让它尽可能简单,所以你可以做rename 's/^123_//' *,它会做同样的事情。干杯。
    • @HodorTheCoder 如果你在 bash (shopt -s globstar) 中设置了 globstar,你可以递归地进行:rename 's/^123_//' **/*
    【解决方案3】:

    如果您想将名为 foo 的文件名中的字符串替换为 bar,您可以在 linux ubuntu 中使用它,根据需要更改文件类型

    find -name "*foo*.filetype" -exec rename 's/foo/bar/' {} ";"
    

    【讨论】:

    • 这是最通用的通用答案
    • 这真的很有效而且很简单。我怎样才能使它递归地替换目录中的文件名。
    【解决方案4】:

    您可以检查“重命名”工具

    例如

    rename 's/^123_//' *.txt
    

    或(需要gawk)

    find . -name '123_*.txt'|awk '{print "mv "$0" "gensub(/\/123_(.*\.txt)$/,"/\\1","g");}'|sh
    

    测试

    kent$  tree
    .
    |-- 123_a.txt
    |-- 123_b.txt
    |-- 123_c.txt
    |-- 123_d.txt
    |-- 123_e.txt
    `-- u
        |-- 123_a.txt
        |-- 123_b.txt
        |-- 123_c.txt
        |-- 123_d.txt
        `-- 123_e.txt
    
    1 directory, 10 files
    
    kent$  find . -name '123_*.txt'|awk '{print "mv "$0" "gensub(/\/123_(.*\.txt)$/,"/\\1","g");}'|sh
    
    kent$  tree
    .
    |-- a.txt
    |-- b.txt
    |-- c.txt
    |-- d.txt
    |-- e.txt
    `-- u
        |-- a.txt
        |-- b.txt
        |-- c.txt
        |-- d.txt
        `-- e.txt
    
    1 directory, 10 files
    

    【讨论】:

    • 你应该提到谐音陷阱:你谈到了特殊的重命名命令,它从 util-linux 中篡夺了 rename 的名称(它不处理正则表达式)。
    • rename 示例不是递归的。 find 示例是,但它不必要地不使用 rename 进行重命名。并且它将生成的脚本通过管道传送到sh,这很恶心。
    • 谢谢,我可以在重命名后将文件名修改为全部小写吗?我的意思是.. 任何 xxx.txt 最终都应该是 xxx.txt?
    【解决方案5】:

    对 Kent 的轻微改动,不需要 gawk 并且更具可读性,(尽管这值得商榷..)

    find . -name "123*" | awk '{a=$1; gsub(/123_/,""); printf "mv \"%s\" \"%s\"\n", a, $1}' | sh

    【讨论】:

    • 这是唯一一个在 FreeNAS 上真正为我工作的。
    • 在 Ubuntu 16.04 上的工作就像一个魅力。谢谢。
    【解决方案6】:

    要扩展Sorpigal's answer,如果你想用hello_替换123_,你可以使用

        find . -name '123*.txt' -type f -exec bash -c 'mv "$1" "${1/\/123_/\/hello_}"' -- {} \;
    

    【讨论】:

      【解决方案7】:

      如果您的文件名中没有换行符:

      find -name '123_*.txt' | while IFS= read -r file; do mv "$file" "${file#123_}"; done
      

      如果您的find 支持-print0 标志(GNU find 支持),则以真正安全的方式:

      find -name '123_*.txt' -print0 | while IFS= read -r -d '' file; do mv "$file" "${file#123_}"; done
      

      【讨论】:

        【解决方案8】:

        您可以为此编写一个小 bash 脚本。 使用以下内容创建一个名为 recursive_replace_filename 的文件:

        #!/bin/bash
        
        if test $# -lt 2; then
          echo "usage: `basename $0` <to_replace> <replace_value>"
        fi
        
        for file in `find . -name "*$1*" -type f`; do
          mv "'$file'" "${file/'$1'/'$2'}"
        done
        

        使可执行文件运行:

        $ chmod +x recursive_replace_filename
        $ ./recursive_replace_filename 123_ ""
        

        请注意,此脚本可能很危险,请确保您知道它在做什么,在哪个文件夹中执行它,以及使用哪些参数。在这种情况下,当前文件夹中所有包含123_的文件都将被重命名。

        【讨论】:

        • 按预期工作。不错!
        【解决方案9】:

        尝试了上面的答案,但它对我不起作用,因为我同时在文件夹和文件名中有字符串,所以这就是我执行以下 bash 脚本:

          for fileType in d f
          do
            find  -type $fileType -iname "stringToSearch*" |while read file
            do
              mv $file $( sed -r "s/stringToSearch/stringToReplaceWith/" <<< $file )
            done
          done
        

        首先我先替换内部文件夹名称,然后替换内部文件名。

        【讨论】:

        • 在文件名中使用空格对我来说效果很好。只需确保在 mv 行中引用两个 var 以使用空格
        【解决方案10】:

        这是迄今为止唯一可行的解​​决方案:

        find-rename-recursive --pattern '123_' --string '' -- . -type f -name "123_*"
        

        所有其他解决方案都不适合我——有些甚至删除了文件!

        详情见https://github.com/l3x/helpers#find-rename-recursive

        【讨论】:

          【解决方案11】:

          使用 util-linux 2.28.2 中的重命名,我不得不使用不同的语法:

          find -name "*.txt" -exec rename -v "123_" ""  {} ";" 
          

          【讨论】:

            【解决方案12】:

            如果名称是固定的,您可以访问每个目录并在子外壳中执行重命名(以避免更改当前目录)相当简单。这就是我如何将一堆new_paths.json 文件重命名为paths.json

            for file in $(find root_directory -name new_paths.json)
              do
                 (cd $(dirname $file) ; mv new_paths.json paths.json)
              done
            

            【讨论】:

              【解决方案13】:

              使用上面的例子,我用它来替换部分文件名...这是用来重命名目录中的各种数据文件,由于供应商更改名称。

              find . -name 'o3_cloudmed_*.*' -type f -exec bash -c 'mv "$1" "${1/cloudmed/revint}"' -- {} \;

              【讨论】:

                【解决方案14】:

                下面,find 命令在树中递归搜索作为第一个参数传递的目录(这里是当前目录“.”)。以下参数是过滤我们要查找的文件的谓词。 “-name”谓词是当前文件名必须匹配的模式(这里我们只想管理以“123_”开头并以“.txt”结尾的文件名。“-type”过滤文件的类型(' f' 表示常规文件,'d' 表示目录...)。“-exec”谓词运行命令行,其中 '{}' 是当前文件名,';' 终止命令。

                find . -name '123_*.txt' -type f -exec bash -c 'name={};mv $name ${name//123_//}' \;
                

                【讨论】:

                • 感谢您的回答,您也可以解释一下您的解决方案吗?
                【解决方案15】:
                prename s/^123_// 123_*
                

                prename in the official Perl5 wiki。为了您的方便,请复制:

                prename - 根据正则表达式重命名文件的脚本。 (原版在哪里发布的?)最初命名为 rename,它主要被发现为 prename,因为原版与 util-linux 包中的 rename 命令冲突。许多分叉和重新实现:

                【讨论】:

                • premane 在我的 bash shell 和命令提示符中无法识别。仅供参考,我已经安装了 perl...
                • 有助于正确拼写命令。阅读我链接到的文档。
                • 我有prename,但它不是递归的。