Bash

使用所有類型的字元重命名大量文件,具有 POSIX 可移植性

  • March 19, 2012

有時我需要重命名一個目錄中的所有文件(稍後將遵循重命名約定),其中文件名始終採用“filenamename.extension”的形式(副檔名始終存在並且會有所不同)。名稱可能包含空格和字元

$$ :graph: $$班級。我的第一個問題是它應該在 *NIX 系統(尤其是 Linux、BSD,後來的其他系統,比如 AIX)之間絕對可移植。我的第二個問題是$$ :graph: $$班級。文件名可以是:

cat.txt
dog_and_cat.txt
Where is the cat?.png
my.cat.is.cute.txt.js.html
;;; ;;; ;;;.......321
áéúő _[a lot of whitespaces]_ óü^^^^^ö.jpg

很容易看出,這些很難處理並放入 for 循環中。例如,

for i in *; do something; done

並不總是喜歡空格和奇怪的字元,尤其是在不同的作業系統中。

重命名約定是將所有文件重命名為某種散列$FOOBAR.$EXTENSION形式,例如 md5sum。$FOOBAR所以在for循環中我有一條線就像

mv $FILE $(md5sum $FILE | sed 's/\ \ .\+//');

它會將文件移動到自身的 md5sum 中,但副檔名消失了。我想保留幾乎總是在.[a-zA-Z0-9]{1,3}表單中的擴展。有時.tar.gz也需要保留一些副檔名(當然我可以將它們添加到變數中,比如MYEXTENSIONS='tar.gz tar.bz2 foo.bar')。

我的直覺告訴我,這個問題可以通過參數化的預設 UNIX/shell 命令解決,但現在對我來說非常困難。我相信我會從答案中學到很多東西。我知道我說過神奇的詞portability,但如果我必須指定語言,則首選 bash 中的解決方案。

實際上,for i in *; do something; done正確處理每個文件名,除了以 a 開頭的文件名.被排除在萬用字元匹配之外。要可移植地匹配所有文件(.和除外),請匹配並跳過由於不匹配模式保持不變而導致的任何不存在的文件。..``* .[!.]* ..?*

如果您遇到問題,可能是因為您後來沒有$i正確引用。**始終在變數替換和命令替換: 周圍加上雙引號"$foo""$(cmd)"**除非您打算進行欄位拆分和通配。

如果您需要將文件名傳遞給外部命令(此處不需要),請注意echo "$foo"不要總是按$foo字面意思列印。少數 shell 執行反斜杠擴展,並且以$foo開頭的少數值-將被視為一個選項。準確列印字元串的安全且符合 POSIX 的方法是

printf '%s' "$foo"

printf '%s\n' "$foo"在末尾添加換行符。需要注意的另一件事是命令替換刪除了尾隨的換行符。如果您需要保留換行符,一個可能的技巧是將非換行符附加到數據中,確保轉換保留此字元,最後截斷此字元。例如:

mangled_file_name="$(printf '%sa' "$file_name" | tr -sc '[:alnum:]-+_.' '[_*]')"
mangled_file_name="${mangled_file_name%a}"

要提取文件的 md5sum,請避免在md5sum輸出中包含文件名,因為這會使其難以剝離。md5sum在的標準輸入上傳遞數據。

請注意,該md5sum命令不在 POSIX 中。一些 unix 變體有md5或根本沒有。cksum是 POSIX 但容易發生衝突。

有關如何獲取文件副檔名的資訊,請參閱抓取文件名中的副檔名。

讓我們把它們放在一起(未經測試)。這裡的一切都可以在任何 POSIX shell 下工作;您可以從 bash 功能中獲得一點,但不多。

for old_name in * .[!.]* ..?*; do
 if ! [ -e "$old_name" ]; then continue; fi
 hash=$(md5sum <"$old_name")
 case "$old_name" in
   *.*.gz|*.*.bz2)                   # double extension
     ext=".${old_name##*.}"
     tmp="${old_name%.*}"
     ext=".${old_name##*.}$ext";;
   ?*.*) ext=".${old_name##*.}";;    # simple extension
   *) ext=;;                         # no extension
 esac
 mv -- "$old_name" "$hash$ext"
done

請注意,我沒有考慮已經存在指定名稱的目標文件的情況。特別是,如果您有現有文件的名稱看起來像您採用的約定,但校驗和部分與文件的內容不匹配,而是與具有相同副檔名的其他文件匹配,那麼會發生什麼將取決於相對詞典順序文件名。

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