我有几个目录,我想通过它们递归迭代并更改文件名
*.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*