我想重命名位于不同文件夹中的多个文件。更具体地说,我想在文件名的中间添加零,以便所有文件名都有三位数字(因此以逻辑顺序显示)。
更具体地说,我有 130 多个文件夹,每个文件夹中有 400 多个 .nii 文件。每个 .nii 文件都具有以下模式:
swu_run1_P_139_Vol_1.nii
- P_###范围从P76到P277(参与者编号)
- Vol_### 范围从 1 到 405(卷号)
由于音量范围从 1 到 405,这意味着任何列表都以 100 开头,而不是 1-405(例如,它开头:100 - 101 - 102 [..] - 109 -10- 110 - 111 等)。解决此问题的一种方法是在文件名中添加零并将所有内容变为三位数,例如:
swu_run1_P_277_Vol_1.nii -> swu_run1_P_277_Vol_001.nii
swu_run1_P_277_Vol_2.nii -> swu_run1_P_277_Vol_002.nii
swu_run1_P_277_Vol_10.nii -> swu_run1_P_277_Vol_010.nii
swu_run1_P_277_Vol_120.nii -> swu_run1_P_277_Vol_120.nii
我对 unix/linux 系统没什么经验,但是使用以前的线程,我设法想出了以下代码。它有两个部分:
1. 重命名多个文件名,在文件名中间添加零:
rename Vol_ Vol_0 *Vol_[0-9].nii
如果我在子文件夹中运行它,我会收到以下错误消息:
Error: rename: swu_run1_P_275_Vol_1.nii: rename to swu_run1_P_275_Vol_01.nii failed: No such file or directory
奇怪的是,它在 Vol_1-9 上加了零。但是,它不会向任何已经有两位或三位数字的数字添加零:
swu_run1_P_277_Vol_1.txt -> swu_run1_P_277_Vol_01.nii
swu_run1_P_277_Vol_10.txt -> swu_run1_P_277_Vol_10.nii
swu_run1_P_277_Vol_100.txt -> swu_run1_P_277_Vol_100.nii
表达式似乎发生了某种奇怪的循环(它试图更改新的 Vol_01,给出错误消息)?为什么它不向两位/三位数字添加零?
2.找到.nii
相关子文件夹中的所有文件,然后进行批量重命名:
find . -iname "*.nii" -execdir rename Vol_ Vol_0 *Vol_[0-9].nii '{}' \;
我对这段代码的理解如下:
find . -iname "*.nii"
搜索当前文件夹和子文件夹中的所有 .nii 文件-execdir
告诉它将以下表达式应用于当前文件夹和子文件夹,即添加零rename Vol_ Vol_0 *Vol_[0-9].nii
添加零(使用以下格式来自文本 到文本 文件列表)'{}'
那里有文件的路径名\;
是否可以结束 -execdir 表达式
如果我尝试在更多文件夹上运行代码,则会收到以下错误消息:
Error: rename: *Vol_[0-9].nii: rename to *Vol_0[0-9].nii failed: No such file or directory
我认为我收到错误消息是因为 -execdir 没有在子文件夹中执行,但我不知道如何解决这个问题。
我不想手动进入每个子文件夹来运行 shell,那么您对如何改进此代码(并使其工作)有什么建议吗?为什么我收到“没有这样的文件或目录”的错误消息?
答案1
swu_run1_P_277_Vol_1.nii
如果我们在变量 中有名称name
,那么${name##*_}
将会是1.nii
(最长的前缀字符串匹配*_
被删除)。
获取并删除后,.nii
我们得到了我们想要零填充到三个字符宽度的数字:
name=swu_run1_P_277_Vol_1.nii
number=${name##*_}
number=${number%.nii}
在bash
shell 中,(可能)对数字进行零填充的最简单方法是printf
:
printf '%.3d' "$number" # would print 001 (with no newline)
我们可以同时构造新名称:
printf '%s_%.3d.nii' "${name%_*.nii}" "$number"
该printf
命令将通过删除原始名称中swu_run1_P_277_Vol_001.nii
匹配的后缀字符串,添加零填充数字,然后添加后缀字符串来打印。_*.nii
_
.nii
最重要的是,我们可以打印结果字符串直接地到一个新变量中printf -v newname ...
。
将它们放在一起作为一个名称:
name=swu_run1_P_277_Vol_1.nii
number=${name##*_}
number=${number%.nii}
printf -v newname '%s_%.3d.nii' "${name%_*.nii}" "$number"
那么这只是一个问题mv "$name" "$newname"
。
好的,那么如何对所有相关文件执行此操作呢?
假设所有相关文件都匹配通配模式*Vol_*.nii
,那么find
,
find . -type f -name '*Vol_*.nii' -exec bash -c '
for pathname do
dirpath=${pathname%/*} # or: dirpath=$(dirname "$pathname")
name=${pathname##*/} # or: name=$(basename "$pathname")
number=${name##*_}
number=${number%.nii}
printf -v newname "%s_%.3d.nii" "${name%_*.nii}" "$number"
printf "Would rename %s --> %s\n" "$pathname" "$dirpath/$newname"
# mv "$pathname" "$dirpath/$newname"
done' bash {} +
这里的内联bash -c
脚本被find
批量调用,这些路径名满足-type f
(是常规文件)和-name
(具有特定名称)条件。该脚本循环遍历这些路径名,并将文件名提取到 中name
,将目录路径名提取到 中dirpath
。
然后它执行与我们之前相同的操作来获得一个新名称,该名称存储在 中newname
,然后重命名该文件。
mv
好吧,为了安全起见,我已经注释掉了实际的命令。您应该先运行一次,看看输出是否正确。如果您使用 GNU 工具,您可能还想mv -b
在存在名称冲突时使用它来备份文件。
附带说明一下,在zsh
shell 中,通配符模式
./**/*Vol_*.nii(n)
将扩展到所有这些名称按数字顺序(并向下递归到 int 子目录):
$ print -rC1 ./**/*Vol_*.nii(n)
./swu_run1_P_277_Vol_1.nii
./swu_run1_P_277_Vol_2.nii
./swu_run1_P_277_Vol_10.nii
./swu_run1_P_277_Vol_120.nii