Linux

甚至可以正確處理所有可能的文件名嗎?

  • April 13, 2018

在 linux 中,文件名“斜杠”和“空字元”中只禁止使用兩個字元。因此,每種腳本語言中具有特殊含義的每個字元都應該被轉義,但是文件名中也允許每個轉義序列!更糟糕的是,即 bash 一些轉義方法只轉義了一些字元,因此要轉義大量不同的字元,您應該一起使用幾種不同的轉義方法,但它們會相互干擾!更糟糕的是,某些命令使用某些字元來實現其目的,而其他命令使用其他字元,因此對於文件的每一個簡單操作,您都應該以不同的方式轉義文件名!更糟糕的是,只能使用空字元來安全地分隔文件名,但大多數命令無法使用它。更糟糕的是,在linux中基本上一切都是文件……

所以告訴我我錯在哪裡……甚至可以正確處理所有可能的文件名嗎?

澄清。最初我想:

  1. 列出給定路徑下的文件和文件夾
  2. 搜尋列表以查找與給定條件(年齡或文件模式或大小)匹配的列表
  3. 將匹配的文件和文件夾移動到類別,即電影由於測試的複雜性,不可能(或實際)在一個命令中完成,所以我不得不在不同的命令之間傳遞文件名。由於文件名中的空格,首先要放棄 Bash globbing。Globbing 總是將帶空格的文件名拆分為列表的兩個元素。然後我嘗試使用“查找”。這更好,但速度慢得多,而且難以使用。

我不能使用任何特殊字元來轉義文件名,因為我不知道文件名中可能包含什麼字元。經過一些測試,我發現任何角色的出現都是時間問題。

我已經嘗試過定義過濾器,例如: audio_ext=(*.mp3 *.wav *.ogg *.mid *.mod *.stm *.s3m *.it *.wma *.669 *.ac3) 很快我就意識到這種方式我不能為多種用途定義過濾器,因為 globbing 踢掉了 rigths。所以我已經禁用了 globbing 和歷史記錄set -fH。沒有 globbing 我不得不手動進行擴展

while IFS= read -r -d $'\0'; do list+=("$REPLY") done < <( find . -maxdepth 1 -mindepth 1 ${params[@]} -print0 2>/dev/null )

params數組在哪裡等"-iname" "*.mp3" "-o" "-iname" "*.wav"。這一直有效,直到文件名稱中有“(”。查找有關錯誤使用的返回錯誤。

說實話…直到最近 15 年來,我一直使用批處理腳本來完成這項任務。花在寫作上的時間大約是一兩個下午。它在文件名方面有缺點和問題!,但通常它有效。現在我已經嘗試了將近兩個月的時間用 bash 編寫它。它醜陋、複雜、漏洞百出,而且它似乎永遠不會好用。

文件名可以使用除 nul 字元 ( \0) 和斜杠以外的任何字元,斜杠是路徑分隔符。變數可以保存任何數據(大多數 shell 中的 nul 字元除外)。如果正確引用,文件名可以安全地儲存在變數中並與實用程序一起使用。

關於你的觀點:

要遍歷一組文件(正常文件或目錄),您可以使用簡單的 shell 循環,例如

for name in ./*; do
   # some code that uses "$name"
done

在使用特定標準選擇特定文件的同時迭代文件find是更好的選擇。例如,要選擇目前目錄(或以下)中所有早於N幾天(修改日期至少N在過去幾天)的正常文件:

find . -type f -mtime +N

類似地,-size用於根據大小選擇文件,並將-name文件名與萬用字元模式匹配。

例如,要選擇文件名匹配*.mov且上週已修改的正常文件:

find . -type f -name '*.mov' -mtime -7

然後,要對這些文件進行實際操作,例如將它們移動到$HOME/Movies目錄中:

find . -type f -name '*.mov' -mtime -7 -exec mv {} "$HOME/Movies" ';'

{}被替換為呼叫mv. 你不需要引用{}(如果你這樣做不會改變任何東西),因為find不會在路徑名上呼叫 shell 的分詞或文件名擴展。

對此的進一步改進是檢測目標目錄中的文件名衝突。為此,我們使用了一個簡短的幫助腳本,它將在其命令行上使用許多文件名:

destdir="$HOME/Movies"
for name do
   if [ -f "$destdir/${name##*/}" ]; then
       printf "%s already exists in %s, not overwriting it!\n" "${name##*/}" "$destdir" >&2
   else
       mv "$name" "$destdir"
   fi
done

或者,以快捷方式:

destdir="$HOME/Movies"
for name do
   [ -f "$destdir/${name##*/}" ] && printf "skipping %s\n" "$name" >&2 && continue
   mv "$name" "$destdir"
done

將其插入我們的find命令:

find . -type f -name '*.mov' -mtime -7 -exec sh -c '
   destdir="$HOME/Movies"
   for name do
       [ -f "$destdir/${name##*/}" ] && printf "skipping %s\n" "$name" >&2 && continue
       mv "$name" "$destdir"
   done' sh {} +

在此過程中,我們不允許 shell 對我們目前正在處理的路徑名或文件名進行分詞或文件名通配。

了解更多資訊:

簡單的。使用 globbing 選擇您想要的文件,並引用保存文件名的變數:

shopt -s nullglob
for file in ./*.txt; do
   do_something_with "$file"
done

這就是它的全部。

更多細節:


更新: globbing不對您看到的分詞效果負責。未能引用變數是。

您可以通過以下方式獲取您的條件的文件資訊stat

read size mtime < <(stat -c "%s %Y" "$file")
[[ $size -gt 1000 ]] && echo "too big"
[[ $mtime -lt $(date -d yesterday +%s) ]] && echo "too old"

更新 2:創建包含許多特殊字元的文件名需要混合各種引用機制,但仍然可以對該文件執行任何操作。

$ filename='~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?`'"'"$' \a\t\n\r\f'".txt"
#          ^^ single quoted part ^^^^^^^^^^^^^^^^   
#                             double quoted part ^^^
#                                ANSI-C quoted part ^^^^^^^^^^^^^^

$ echo "$filename"
~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?`'   

.txt

$ printf "%q\n" "$filename"
$'~ASDFzxcv!@#$%^&*()_+[]\\{}|;:",.<>?`\' \a\t\n\r\f.txt'

$ date > "$filename"

$ cat "$filename"
Thu Apr 12 15:14:29 EDT 2018

$ ls -lt
total 3836
-rw-rw-r-- 1 jackman jackman      29 Apr 12 15:14 ~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?`' ?????.txt
               ︙

$ ls -lt --show-control-chars
total 3836
-rw-rw-r-- 1 jackman jackman      29 Apr 12 15:14 ~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?`'     

.txt
               ︙

如果 的輸出ls被重定向到終端以外的任何東西(例如,文件或管道),它將--show-control-chars預設使用該樣式。你可以通過執行看到這一點ls -lt | cat。  ls有其他顯示選項;例如,。--quoting-style=*WORD*

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