使用多種模式執行ls
時避免非零退出程式碼
假設我有兩個可能的路徑,我想在 Linux 機器上列出目錄和文件:
/some/path1/ /some/path2/
如果我在 中執行以下操作
tcsh
,我會得到0
退出程式碼,如果至少有一個path1
或path2
存在:ls -d /some/{path1,path2}/*
但是,如果我在 中執行完全相同的操作
bash
,我會得到2
退出程式碼,並顯示stderr
消息報告path1
不存在(如果 path1 是不存在的那個)。在這種情況下,我該如何
bash
表現tcsh
?如果至少存在一條路徑,ls
我可以要求它回饋嗎?0
如果兩者都不存在,我確實期望非零程式碼,這就是tcsh
回饋。
您的大部分問題已經在為什麼 nullglob 不是預設值?.
要記住的一件事是:
ls -d /some/{path1,path2}/*
其中
csh
/tcsh
/zsh
/bash
/ksh
(但不是fish
,見下文)與以下內容相同:ls -d /some/path1/* /some/path2/*
由於大括號擴展是在glob 擴展之前執行的(不是作為.
/some/pathx/*``ls
bash
,就像ksh
它主要複製的一樣,它繼承了 Bourne shell 引入的錯誤功能,即當 glob 模式與任何文件都不匹配時,它按原樣傳遞,字面上作為命令的參數。因此,在這些 shell 中,如果
/some/path1/*
匹配至少一個文件並且/some/path2/*
不匹配任何文件,ls
則將使用-d
、/some/path1/file1
和/some/path1/file2
文字/some/path2/*
作為參數呼叫。由於/some/path2/*
文件不存在,ls
會報錯。
csh
其行為類似於早期的 Unix 版本,其中 glob 不是由 shell 執行,而是由/etc/glob
輔助實用程序(將它們的名稱命名為 glob)執行。該助手將在呼叫命令之前執行 glob 擴展,如果所有glob 模式都無法匹配任何文件,則在No match
不執行命令的情況下報告錯誤。否則,只要至少有一個匹配的 glob,所有不匹配的都會被簡單地刪除。因此,在我們上面的範例中,使用
csh
/tcsh
或古老的 Thompson shell 及其/etc/glob
助手,ls
將使用-d
,/some/path1/file1
和/some/path1/file2
only 呼叫,並且可能會成功(只要/some/path1
是可搜尋的)。
zsh
既是 Korn-like 又是 csh-like shell。它沒有 Bourne shell 的錯誤功能,即不匹配的 glob 按原樣傳遞¹,但預設情況下比csh
這更嚴格,所有失敗的 glob 都被視為致命錯誤。因此zsh
,在預設情況下,如果其中一個/some/path1/*
或/some/path2/*
(或兩者)不匹配,則該命令被中止。可以使用option²在bash
shell中啟用類似的行為。failglob
這使得行為更加可預測/一致,但意味著當您想要將多個 glob 擴展傳遞給命令並且不希望它失敗時,只要其中一個 glob 成功,您就會遇到該問題。但是,您可以設置
cshnullglob
選項以獲得類似 csh 的行為(或emulate csh
)。這可以通過使用匿名函式在本地完成:
() { set -o localoptions -o cshnullglob; ls -d /some/{path1,path2}/*; }
或者只是使用一個子shell:
(set -o cshnullglob; ls -d /some/{path1,path2}/*)
但是,在這裡,您可以使用交替 glob 運算符來使用與所有它們匹配的一個,而不是使用兩個 glob:
ls -d /some/(path1|path2)/*
在這裡,您甚至可以這樣做:
ls -d /some/path[12]/*
在
bash
中,您可以啟用extglob
bash 選項以支持 ksh 的擴展 glob 運算符的子集,包括交替:(shopt -s extglob; ls -d /some/@(path1|path2)/*)
現在,由於從 Bourne shell 繼承的錯誤功能,如果該 glob 不匹配任何文件,
/some/@(path1|path2)/*
將按原樣傳遞給ls
並ls
最終列出一個名為literal 的文件/some/@(path1|path2)/*
,因此您還需要啟用failglob
選項謹防:(shopt -s extglob failglob; ls -d /some/@(path1|path2)/*)
或者,您可以使用
nullglob
選項(從bash
複製zsh
)將所有不匹配的 glob 擴展為空。但:(shopt -s nullglob; ls -d /some/path1/* /some/path2/*)
在命令的特殊情況下是錯誤的
ls
,如果沒有傳遞任何參數列表.
。但是,您可以使用nullglob
將 glob 擴展儲存到數組中,並且僅ls
在數組成員為非空時將其作為參數呼叫:( shopt -s nullglob files=( /some/path1/* /some/path2/* ) if (( ${#files[@]} > 0 )); then ls -d -- "${files[@]}" else echo >&2 No match exit 2 fi )
在
zsh
中,而不是全域啟用,您可以使用glob 限定符(啟發 ksh’s ,尚未複製)nullglob
在每個 glob 的基礎上啟用它,並再次使用匿名函式而不是數組:(N)``~(N)``bash
() { (( $# > 0 )) && ls -d -- "$@" } /some/path1/*(N) /some/path2/*(N)
shell 現在的
fish
行為類似於zsh
失敗的 glob 導致錯誤的地方,除非 glob 與for
,set
(用於分配數組)一起使用或count
它以某種nullglob
方式表現。此外,在 中
fish
,雖然大括號擴展本身不是 glob 運算符,但它是作為通配的一部分完成的,或者當大括號擴展與通配相結合併且至少可以返回一個元素時,至少一個命令不會中止。所以,在
fish
:ls -d /some/{path1,path2}/*
最終會表現得像 in
csh
。甚至:
{ls,-d,/xx*}
如果不匹配而不是失敗(在這種情況下與 csh 表現不同),將導致單獨
ls
呼叫。-d``/xx*
無論如何,如果只是
ls
. 在zsh
中,您可以使用其print -rC3 /some/{path1,path2}/*(N)
將在 3 列上列印路徑
r
aw(如果與N
ullglob glob 限定符不匹配,則不列印任何內容)。相反,如果您想檢查這兩個目錄中是否至少有一個非隱藏文件,您可以執行以下操作:
# bash if (shopt -s nullglob; set -- /some/path1/* /some/path2/*; (($#))); then echo yes else echo no fi
或者使用一個函式:
has_non_hidden_files() ( shopt -s nullglob set -- "$1"/* (($#)) ) if has_non_hidden_files /some/path1 || has_non_hidden_files /some/path2 then echo yes else echo no fi
# zsh if ()(($#)) /some/path1/*(N) /some/path2/*(N); then echo yes else echo no fi
或者有一個功能:
has_non_hidden_files() ()(($#)) $1/*(NY1) if has_non_hidden_files /some/path1 || has_non_hidden_files /some/path2 then echo yes else echo no fi
(
Y1
作為找到第一個文件後停止的優化)請注意
has_non_hidden_files
,對於使用者無法讀取的目錄(無論它們是否有文件),它們會(默默地)返回 false。在中,您可以通過其特殊變數zsh
檢測到這種情況。$ERRNO
¹ Bourne 行為(由 POSIX 指定)可以
zsh
通過 doemulate sh
或emulate ksh
或 with啟用set +o nomatch
² 請注意,當 glob 不匹配時,關於取消的確切內容存在顯著差異,這種
fish
行為通常更明智,並且bash -O failglob
可能是最糟糕的