攜帶式檢查空目錄
使用 Bash 和 Dash,您可以僅使用 shell 檢查空目錄(忽略點文件以保持簡單):
set * if [ -e "$1" ] then echo 'not empty' else echo 'empty' fi
然而,我最近了解到 Zsh 在這種情況下失敗了:
% set * zsh: no matches found: * % echo "$? $#" 1 0
因此,該
set
命令不僅失敗,而且甚至沒有設置$@
. 我想我可以測試 if$#
is0
,但似乎 Zsh 甚至停止執行:% { set *; echo 2; } zsh: no matches found: *
與 Bash 和 Dash 比較:
$ { set *; echo 2; } 2
這可以以在 bash、dash 和 zsh 中工作的方式完成嗎?
有幾個問題
set * if [ -e "$1" ] then echo 'not empty' else echo 'empty' fi
程式碼:
- 如果啟用該
nullglob
選項(從zsh
但現在大多數其他 shell 支持),set *
則變為set
列出所有 shell 變數(以及某些 shell 中的函式)- 如果第一個非隱藏文件的名稱以
-
or開頭+
,它將被視為選項set
。這兩個問題可以通過使用set -- *
來解決。*
僅擴展非隱藏文件,因此不是測試目錄是否為空,而是測試它是否包含非隱藏文件。對於某些 shell,您可以使用dotglob
orglobdot
選項或FIGNORE
根據 shell 使用特殊變數來解決此問題。[ -e "$1" ]
測試stat()
系統呼叫是否成功。如果第一個文件是指向不可訪問位置的符號連結,則返回 false。您不需要stat()
(甚至不需要lstat()
)任何文件來知道目錄是否為空,只需檢查它是否有一些內容。*
擴展涉及打開目前目錄,檢索所有條目,儲存所有非隱藏條目並對其進行排序,這也是非常低效的。檢查目錄是否為非空(除
.
and之外的任何條目..
)的最有效方法zsh
是使用F
glob 限定符(F
對於full,此處表示非空):if [ .(NF) ]; then print . is not empty else print "it's empty or I can't read it" fi
N
是nullglob
全域限定符。因此.(NF)
擴展到.
if.
is full 而沒有其他內容。在
lstat()
目錄上,如果zsh
發現它的連結計數大於2,則意味著它至少有一個子目錄所以不為空,所以我們甚至不需要打開那個目錄(這也意味著,在那個即使我們沒有讀取權限,我們也可以知道該目錄是非空的)。否則,zsh 打開目錄,讀取其內容並停在第一個既不是.
也不..
需要讀取、儲存或排序所有內容的條目處。使用 POSIX shell(
zsh
僅在仿真中表現(更多)POSIXlysh
),僅使用 glob 檢查目錄是否非空是非常尷尬的。一種方法是:
set .[!.]* '.[!.]'[*] .[.]?* [*] * if [ "$#$1$2$3$4$5" = '5.[!.]*.[!.][*].[.]?*[*]*' ]; then echo "empty or can't read" else echo not empty fi
(假設沒有從預設值更改與 glob 相關的選項(POSIX 僅指定
noglob
)並且未設置GLOBIGNORE
(forbash
) 和FIGNORE
(forksh
) 變數,並且 (foryash
) 沒有任何文件名包含不形成有效字元的字節序列)。這個想法是,在 POSIX shell 中,當一個 glob 不匹配時,它不會被擴展(Bourne shell 在 70 年代後期引入的錯誤功能)。因此
set -- *
,如果我們得到$1
==*
,我們不知道是因為沒有匹配項還是有一個名為*
.您解決此問題的(有缺陷的)方法是使用
[ -e "$1" ]
. 在這裡,我們使用set -- [*] *
. 這可以消除這兩種情況的歧義,因為如果沒有文件,上面將保留[*] *
,如果有一個名為 的文件*
,則變為* *
. 我們對隱藏文件做類似的事情。這有點尷尬,因為 Bourne shell 的另一個錯誤功能(也由Forsyth shellzsh
、fish
pdksh和.*``.``..``readdir()
因此,要使其在所有這些 shell 中工作,您可以執行以下操作:
cwd_empty() if [ -n "$ZSH_VERSION" ]; then eval '! [ .(NF) ]' else set .[!.]* '.[!.]'[*] .[.]?* [*] * [ "$#$1$2$3$4$5" = '5.[!.]*.[!.][*].[.]?*[*]*' ] fi
在任何情況下,
zsh
預設情況下的語法都與 POSIX sh 語法不兼容,因為它以非向後兼容的方式修復了 Bourne shell(遠在 POSIX.2 首次發布之前)中的大多數主要問題,包括*
當沒有匹配時保持未擴展(Bourne 之前的 shell 沒有那個問題,csh
並且tcsh
也fish
沒有),並且.*
包括.
和..
但其他幾個,比如在參數或算術擴展上執行的 split+glob,所以你不能指望程式碼用 POSIX sh 編寫以始終工作,zsh
除非您打開sh
仿真。該
sh
仿真尤其如此,因此您可以在zsh
.如果你想在 POSIX裡面
source
寫一個文件,你可以這樣做:sh``zsh
emulate sh -c 'source that-file'
然後該文件將在
sh
仿真中進行評估,並且在其中聲明的任何函式都將保留該仿真模式。
雖然 zsh 的預設行為是給出錯誤,但這是由
nomatch
選項控制的。您可以取消設置選項,以 bash 和 dash 的方式保留*
原位:setopt -o nonomatch
雖然該命令在其他任何一個中都不起作用,但您可以忽略它:
setopt -o nonomatch 2>/dev/null || true ; set *
這
setopt
在 zsh 上執行,並在其他命令上抑制了失敗命令的錯誤輸出 (2>/dev/null
) 和返回程式碼 (|| true
)。如所寫,如果有文件,例如
-e
: 那麼您將執行set -e
並更改 shell 選項以在命令失敗時終止;如果你有創造力,結果會更糟。set -- *
將更安全並防止選項更改。