在 shell 中重命名具有多個(包括複合)副檔名的文件集
我有一個包含很多副檔名但名稱唯一的文件集列表。
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
也會好的。還有一些問題:
- 如何從文件開頭定位到 .foo?
- 我如何循環創建的變數,例如
result=$(openssl rand -hex 8)
為了在重命名中使用它,並且只有當一個集合完成時,才能再次分配它以循環它直到下一個集合完成,等等?謝謝!
這個問題有幾個部分:
- 將每個文件名分解為基本部分和副檔名。
- 對每個名稱的基本部分應用一致的轉換。
- 根據所選的基礎部分轉換重命名文件,保留副檔名。
1.分解文件名
從您的範例名稱中,您認為文件名的基本部分並不完全清楚。分隔符顯然是一個點,但在一個例子中
yet.a.different.file.name.foo.bar1-2.baz
,哪個點?您提到了使用 的嘗試*.(<->|baz|bar<->.baz)
,它不會將foo
orbar1-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}'