甚至可以正確處理所有可能的文件名嗎?
在 linux 中,文件名“斜杠”和“空字元”中只禁止使用兩個字元。因此,每種腳本語言中具有特殊含義的每個字元都應該被轉義,但是文件名中也允許每個轉義序列!更糟糕的是,即 bash 一些轉義方法只轉義了一些字元,因此要轉義大量不同的字元,您應該一起使用幾種不同的轉義方法,但它們會相互干擾!更糟糕的是,某些命令使用某些字元來實現其目的,而其他命令使用其他字元,因此對於文件的每一個簡單操作,您都應該以不同的方式轉義文件名!更糟糕的是,只能使用空字元來安全地分隔文件名,但大多數命令無法使用它。更糟糕的是,在linux中基本上一切都是文件……
所以告訴我我錯在哪裡……甚至可以正確處理所有可能的文件名嗎?
澄清。最初我想:
- 列出給定路徑下的文件和文件夾
- 搜尋列表以查找與給定條件(年齡或文件模式或大小)匹配的列表
- 將匹配的文件和文件夾移動到類別,即電影由於測試的複雜性,不可能(或實際)在一個命令中完成,所以我不得不在不同的命令之間傳遞文件名。由於文件名中的空格,首先要放棄 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*