Bash

如何在不發生衝突的情況下移動文件名編號?

  • April 30, 2019

考慮這個文件列表:

$ 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.txtindex-0.txt 之前重命名index-2.txtindex-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。我們可以使用GNU tac(一些系統也有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 字元。

強韌性留給讀者作為練習;-)

引用自:https://unix.stackexchange.com/questions/48848