Zsh

在 shell 中重命名具有多個(包括複合)副檔名的文件集

  • October 28, 2021

我有一個包含很多副檔名但名稱唯一的文件集列表。

filename-1.foo.001
...
filename-1.foo.020
filename-1.foo.baz
filename-1.foo.bar1-2.baz
...
filename-1.foo.bar7-8.baz

another_filename.foo.001
another_filename.foo.002
...
another_filename.foo.009
another_filename.foo.baz
another_filename.foo.bar1-2.baz
another_filename.foo.bar3-4.baz
another_filename.foo.bar4-5.baz
another_filename.foo.bar7-8.baz

yet.a.different.file.name.foo.001
yet.a.different.file.name.foo.002
...
yet.a.different.file.name.foo.287
yet.a.different.file.name.foo.baz
yet.a.different.file.name.foo.bar1-2.baz
yet.a.different.file.name.foo.bar3-4.baz
yet.a.different.file.name.foo.bar4-5.baz
yet.a.different.file.name.foo.bar7-8.baz

moreFileNaming.foo.001
...
moreFileNaming.foo.009
moreFileNaming.foo.baz
moreFileNaming.foo.bar1-2.baz
moreFileNaming.foo.bar3-4.baz
moreFileNaming.foo.bar4-5.baz
moreFileNaming.foo.bar7-8.baz

我想使用 的輸出重命名這些文件,openssl rand -hex 8以獲取每個集合的隨機文件名,如下所示:

9874f7187c914ea9.foo.001
...
9874f7187c914ea9.foo.020
9874f7187c914ea9.foo.baz
9874f7187c914ea9.foo.bar1-2.baz
...
9874f7187c914ea9.foo.bar7-8.baz

2f54a0b6528e3927.foo.001
2f54a0b6528e3927.foo.002
...
2f54a0b6528e3927.foo.009
2f54a0b6528e3927.foo.baz
2f54a0b6528e3927.foo.bar1-2.baz
2f54a0b6528e3927.foo.bar3-4.baz
2f54a0b6528e3927.foo.bar4-5.baz
2f54a0b6528e3927.foo.bar7-8.baz

71ad0aa90148b2f5.foo.001
71ad0aa90148b2f5.foo.002
...
71ad0aa90148b2f5.foo.287
71ad0aa90148b2f5.foo.baz
71ad0aa90148b2f5.foo.bar1-2.baz
71ad0aa90148b2f5.foo.bar3-4.baz
71ad0aa90148b2f5.foo.bar4-5.baz
71ad0aa90148b2f5.foo.bar7-8.baz

3721323156e921b5.foo.001
...
3721323156e921b5.foo.009
3721323156e921b5.foo.baz
3721323156e921b5.foo.bar1-2.baz
3721323156e921b5.foo.bar3-4.baz
3721323156e921b5.foo.bar4-5.baz
3721323156e921b5.foo.bar7-8.baz

我已經嘗試過for name (*.(<->|baz|bar<->.baz) result=$(openssl rand -hex 16) && mv $name $result(這可能無法正常工作,因為它在幾次迭代之前)但是當它工作時它會給每個文件一個隨機名稱,我只希望每個集合保持相同的名稱,只是隨機的並且具有相同的大小。Sha1sum 或任何其他方法也可以。

我該如何做到這一點?特別是對於文件*.foo.bar* -*.baz ?

如果我們放棄 foo

3721323156e921b5.001
...
3721323156e921b5.009
3721323156e921b5.baz
3721323156e921b5.bar1-2.baz
3721323156e921b5.bar3-4.baz
3721323156e921b5.bar4-5.baz
3721323156e921b5.bar7-8.baz

也會好的。還有一些問題:

  1. 如何從文件開頭定位到 .foo?
  2. 我如何循環創建的變數,例如result=$(openssl rand -hex 8)為了在重命名中使用它,並且只有當一個集合完成時,才能再次分配它以循環它直到下一個集合完成,等等?

謝謝!

這個問題有幾個部分:

  1. 將每個文件名分解為基本部分和副檔名。
  2. 對每個名稱的基本部分應用一致的轉換。
  3. 根據所選的基礎部分轉換重命名文件,保留副檔名。

1.分解文件名

從您的範例名稱中,您認為文件名的基本部分並不完全清楚。分隔符顯然是一個點,但在一個例子中yet.a.different.file.name.foo.bar1-2.baz,哪個點?您提到了使用 的嘗試*.(<->|baz|bar<->.baz),它不會將fooorbar1-2視為擴展。允許它們作為擴展的調整是.(foo|<->|baz|bar<->(|-<->).baz). $f然後您可以按如下方式拆分文件名:

setopt extended_glob
base=${f%%(.(<->|baz|bar<->(|-<->).baz))#}; extensions=${f#$base}

或者,如果可以接受將 base 定義為 first 之前的所有內容,並且不包括 first .foo.,則分解更簡單:

base=${f%*.foo.*}; extensions=${f#$base}

2. 應用一致的轉換

如果要應用確定性轉換,則每次都可以重新計算。例如,您可以通過使用密鑰獲取名稱的 MAC 來獲得偽隨機結果,每次使用相同的密鑰。

secret=$(openssl rand -hex 32)
for … # Loop over the files as per (3.), set $base and $extensions as per (1.)
 new_base=${"$(openssl dgst -sha256 -hmac $secret <<<$base)"[-16,-1]}

ps(注意:如果其他使用者在執行時執行,則密鑰將對其他使用者可見openssl。我認為這對您來說不是問題,但未來的讀者應該注意這一點。)

如果你想應用隨機變換,你需要記住每個基映射到什麼。有兩種方法可以做到這一點:

  • 您可以按文件基數對文件進行分組,然後一次處理一個基數。
  • 您可以逐個處理文件,但請記住每個基礎映射到的內容,並且僅在尚未看到基礎時才生成新映射。

第二種方法更簡單,第一種方法沒有特別的優勢,所以我只展示第二種方法。

建立一個關聯數組將鹼基映射到新鹼基。

typeset -A mapping
mapping=()
for … # Loop over the files as per (3.), set $base and $extensions as per (1.)
 if ((!$+mapping[$base])); then
   mapping[$base]=$(openssl rand -hex 8)
 fi
 new_base=$mapping[$base]

3.重命名文件

Zsh 帶有一個非常有用的文件重命名工具:zmv. 您要進行的轉換非常複雜,以至於 zmv 不會讓它變得微不足道:文件名分解和轉換都需要額外的工作。即使在您的情況下, zmv 也有一些小優勢。特別是,如果發生衝突, zmv 將出錯(由於隨機因素,除非您使用較短的長度,否則極不可能發生)。但是由於名稱轉換比較困難,使用zmv比較尷尬,簡單的循環比較容易寫。

這是使用隨機名稱的完整片段。

setopt extended_glob
typeset -A mapping
mapping=()
for f in *.(foo|<->|baz|bar<->(|-<->).baz); do
 base=${f%%(.(foo|<->|baz|bar<->(|-<->).baz))#}; extensions=${f#$base}
 if ((!$+mapping[$base])); then
   mapping[$base]=$(openssl rand -hex 8)
 fi
 new_base=$mapping[$base]
 mv -i -- $f $new_base$extensions
done

這是使用給定值的確定性名稱的完整片段$secret

setopt extended_glob
secret=$(openssl rand -hex 32)
for f in *.(foo|<->|baz|bar<->(|-<->).baz); do
 base=${f%%(.(foo|<->|baz|bar<->(|-<->).baz))#}; extensions=${f#$base}
 new_base=${"$(openssl dgst -sha256 -hmac $secret <<<$base)"[-16,-1]}
 mv -i -- $f $new_base$extensions
done

這是一個用於確定性案例的單行程式碼zmv,使用第一個.foo.來標記基數的結束。旗幟-w有助於分解。

autoload zmv
secret=$(openssl rand -hex 32)
zmv -w '*.foo.*' '${"$(openssl dgst -sha256 -hmac $secret <<<$1)"[-16,-1]}.foo.$2'

在隨機情況下使用 zmv 比較棘手,因為我們需要保留從一個轉換步驟到下一個轉換步驟的資訊。我們可以將一些程式碼打包到命令替換zmv … '$(…; if …; then mapping[$base]=…; …)'中,因為賦值mapping將在命令替換子shell內,因此只會在子shell內產生影響。但是,我們可以使用條件參數賦值${name=word}mapping[$base]僅在未設置時進行設置。

typeset -A mapping; mapping=()
zmv -w '*.foo.*' '${mapping[${1}]=$(openssl rand -hex 16)}.foo.$2'

將 zmv 與不利用 的分解一起使用.foo,就像上面 (1.​​) 中更複雜的範例一樣,會產生更複雜的程式碼。僅出於範例的目的,這裡是確定性情況的 zmv 呼叫,base用作儲存基本名稱的臨時變數。它用於${name::=word}在參數擴展期間分配給變數,並${…}[0]從擴展中抑制該部分([0]從第 0 個字元獲取子字元串,由於 zsh 從 1 開始對數組元素和字元串字元進行編號;類似的東西[2,1]也可以工作)。

zmv  '*.(<->|baz|bar<->.baz)' '${${base::=${f%%(.(<->|baz|bar<->(|-<->).baz))#}}[0]}${"$(openssl dgst -sha256 -hmac $secret <<<$base)"[-16,-1]}.${f#$base}'

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