如何在多个目录中递归地将一个文件中的模式重命名为另一个文件

如何在多个目录中递归地将一个文件中的模式重命名为另一个文件

我有几个目录,我想通过它们递归迭代并更改文件名

*.GEOT14246.*

*.GEOT15000.*

bash 脚本上是否有一个行可以执行此操作,或者我是否必须编写一个带有 for 循环的脚本。我的尝试(不起作用)如下:

#!/bin/bash

for file in `find . -type f -name "*.GEOT14246.*"`  
do
    echo "file = $file"
    mv $file *.GEOT15000.*
done

然后我打电话:

cd /path/to/script/
sh ./script1.sh /path/to/starting/dir

显然这不起作用,因为我认为我没有将起始目录路径作为参数传递。我对 Unix 很陌生,那么如何向它传递参数来说明它应该从哪个目录开始搜索以及如何让脚本工作?

答案1

这应该有效:

find . -type f -name "*.GEOT14246.*" -print0 | \
xargs -0 rename 's/GEOT14246/GEOT15000/'

鉴于没有名为的目录*.GEOT14246.*


使用 find 的 bash 变体可能类似于:

while IFS= read -r -d $'\0' file; do
    printf "MV: %-40s => %s\n" "$file" "${file/GEOT14246/GEOT15000}"
    mv "$file" "${file/GEOT14246/GEOT15000}"
done < <(find . -type f -name "*.GEOT14246.*" -print0)
              |                                 |
              +--- this is starting directory   +--- This ensures no hiccup
                                                     if newline etc. in name

相对但完整的路径是从 find 传递的——您应该从 printf语句中看到这一点。

新名称是使用 bash 编译的:

${some_variable/find/replace}

替换所有find的使用:

${some_variable//find/replace}

ETC。更多这里。这也可能是一本不错的读物:Bash 陷阱,使用查找

阅读一些指南,例如Bash指南。在网上找到一些教程,但通常永远不会相信它们,请在此处或在 irc.freenode.net #bash 上询问。


您不需要通过调用 来调用脚本sh。这也称为 Bourne shell (sh),而不是 Bourne Again shell (bash)。如果你打算用 bash 运行它;问题bash file.sh扩展也不合适。

您通常所做的就是通过以下方式使文件可执行:

chmod +x script_file

然后运行它:

./script_file

舍邦负责脚本应该在什么环境中运行。


在您的脚本中,您不会在任何地方使用传递的路径名。该脚本有一个“参数列表”,从$0该列表开始是脚本名称、$1第一个参数、$2第二个参数,依此类推。

在您的脚本中,您将按照以下方向执行某些操作:

# Check if argument 1 is a directory, if not exit with error
if ! [[ -d "$1" ]]; then
    printf "Usage: %s [PATH]\n" "$(basename "$0")" >&2
    exit 1
fi

# OK
path="$1"

while ...

done < <(find "$path" -type f ...)

您当前的mv语句会将所有文件移动到您发出命令的任何位置 - 一个名为.GEOT14246。(如覆盖每个mv语句):

脚本运行前:

$ tree
.
└── d1
    ├── a1
    │   ├── a2
    │   │   ├── a3
    │   │   │   └── foo.GEOT14246.faa
    │   │   └── foo.GEOT14246.faa
    │   └── foo.GEOT14246.faa
    └── b1
        ├── b2
        │   ├── b3
        │   │   └── foo.GEOT14246.faa
        │   └── foo.GEOT14246.faa
        └── foo.GEOT14246.faa

脚本运行后:

$ tree
.
├── d1
│   ├── a1
│   │   └── a2
│   │       └── a3
│   └── b1
│       └── b2
│           └── b3
└── *.GEOT15000.*

另外,带有空格或其他有趣的东西的文件/路径也会使脚本变坏并造成严重破坏。因此,您必须引用变量(例如"$file")。

答案2

在 bash 4.0 或更高版本中,您还可以使用 globstar:

shopt -s globstar
for f in **/*GEOT14246*; do mv "$f" "$f{/14246/15000}"; done
rename s/14246/15000/ **/*GEOT14246*
  • glob 模式还匹配包含 GEOT14246 的文件夹
  • 两个选项都适用于整个路径
  • 如果 glob 模式扩展的长度超过getconf ARG_MAX

或者使用查找并读取:

find . -name '*GEOT14246*' | while read f; do mv "$f" "${f/14246/15000}"; done
  • -print0如果路径不包含换行符则不需要
  • IFS=如果路径不以 IFS 中的字符开头或结尾,则不需要
  • -r如果路径不包含反斜杠则不需要
  • < <()如果您不修改 while 循环之外的变量或其他内容,则不需要

答案3

bash 中没有简单的单行代码,但 zsh 中有。将行添加autoload -U zmv到您的~/.zshrc.然后你可以使用zmv和 zsh 的通配符参数替换

zmv '/path/to/starting/dir/**/*GEOT14246*' '${f//GEOT14246/GEOT15000}'

请注意,这GEOT14246也会替换文件名。您需要将两个参数括起来,因为zmv要再次解析它们。

在 Linux 上,通过 bash 或 zsh,您可以使用rename反而。这仅替换GEOT14246每个路径中第一次出现的。

rename GEOT14246 GEOT15000 /path/to/starting/dir/**/*GEOT14246*

在 bash 中,您需要**首先启用递归 glob shopt -s globstar(将此行添加到您的~/.bashrc)。

如果您的 Linux 发行版是 Debian、Ubuntu 或其衍生版本,rename命令是不同的。使用以下任一方法:

rename.ul GEOT14246 GEOT15000 /path/to/starting/dir/**/*GEOT14246*
rename 's/GEOT14246/GEOT15000/g' /path/to/starting/dir/**/*GEOT14246*

相关内容