Bash
如何在不發生衝突的情況下移動文件名編號?
考慮這個文件列表:
$ touch index-{1,2,3,4,5,6,7,8,9}.txt
如果我想將它們向下移動以使它們從零開始,則相對容易:
$ rename --verbose 's/^index-([1-9])\.txt$/$1/; $_="index-" . ($_ - 1) . ".txt"' index-*.txt
這是有效的,因為
man bash
指定 glob 按字母順序排序,因此它會在重命名index-1.txt
為index-0.txt
之前重命名index-2.txt
為index-1.txt
。如果您想向上移動,或者如果數字有不同的長度,這會發生故障:
$ touch index-{10,11}.txt $ rename --verbose 's/^index-([0-9]+)\.txt$/$1/; $_="index-" . ($_ + 1) . ".txt"' index-*.txt index-10.txt not renamed: index-11.txt already exists index-11.txt renamed as index-12.txt index-1.txt not renamed: index-2.txt already exists
可能的長期修復:
rename
嘗試重新排序操作直到沒有衝突的選項。rename
首先將文件移動到臨時目錄,然後使用新名稱將它們移回的選項。- 分兩步重命名文件的
rename
選項,在第一步中使用不會與原始名稱或新名稱衝突的唯一名稱。- 一種對 Bash glob進行自然排序 +反轉的方法。
有沒有一種簡單的方法來解決這個問題?
@l0b0 的解決方案被重寫以獲得更好的強韌性:
printf '%s\0' index-*.txt | sort --zero-terminated --field-separator - --key 2rn | xargs -0r rename --verbose ' s/^index-([0-9]+)\.txt$/$1/; $_="index-" . ($_ + 1) . ".txt"'
隨意包含在您的解決方案中,然後我將刪除我的答案。
請注意,@l0bo 的解決方案是 GNU 特定的,而不是 Unix(GNU’s Not Unix)。
在 Unix 上有一個鮮為人知的標準命令,可以幫助我們在這裡找到一個通用的解決方案,以找出必須按什麼順序進行一系列重命名:
tsort
假設我們有一個要在名為的文件中完成的重命名列表
renames.txt
(假設為了展示,它們的名稱不包含空格):d a e f b e a c
因為
d
是要改名的a
,也就是說a
必須改名之前d
。所以我們有一個部分排序順序,這將與文件重命名的順序相反。
tsort
是從部分排序順序列表中推斷完整排序順序的工具。如果有一個循環可以幫助我們檢測沒有解決方案的情況,它將返回一個錯誤。如果我們應用tsort
該輸入,它會給我們:b d e a f c
其中說
b
應該在 afterd
之後重命名e
。我們可以使用GNUtac
(一些系統也有tail -r
)來顛倒這個順序:c f a e d b
並加入我們的重命名列表:
tsort renames.txt | tac | awk ' NR==FNR { ren[$1] = $2 next } $1 in ren { print "mv -i --", $1, ren[$1] }' renames.txt -
這給了我們:
mv -i -- a c mv -i -- e f mv -i -- d a mv -i -- b e
然後我們可以通過管道
sh
執行。但是請注意,上面的程式碼並不健壯,因為我們沒有檢查
tsort
上面的退出狀態來檢測循環,並且文件名不能包含任何特殊的 shell 字元。強韌性留給讀者作為練習;-)