
我想重命名位於不同資料夾中的多個檔案。更具體地說,我想在檔案名稱的中間添加零,以便所有檔案名稱都有三位數字(因此以邏輯順序顯示)。
更具體地說,我有 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