假設我有以下程式碼:
# Check if the color prompt is enabled and supported on this system
if [ -n "$force_color_prompt" ] && [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
GREEN="\033[1;32m"
DIM="\033[2m"
RESET="\033[00m"
fi
echo -e "Oh, ${GREEN}green${RESET} world, ${DIM}don't desert me now...${RESET}"
如果啟用顏色支持,它將回顯出漂亮的彩色線條。如果未啟用顏色支持,${GREEN}
則不會設定類似的值,並且文字將以通常的白色和黑色背景列印出來。
該程式碼依賴於這樣一個事實:未設定的變數將簡單地計算為空字串(在我的測試中,它們確實如此)。這是否會導致某些系統出現錯誤或問題,或者是否會導致所有不存在的變數總是評估為空字串?我有什麼理由不該依賴這個機制嗎?
答案1
$FOO
當擴展為or (等效)時,不存在的變數將始終計算為空字串${FOO}
,並且依賴它沒有什麼壞處,除了在一種特殊情況下:
set -u
如果有人在您嘗試使用該變數之前在當前 shell 中呼叫過,則他們已啟用此設定:
-u 執行參數時將未設定的變數視為錯誤 進一步擴展。如果嘗試在未設定的情況下進行擴展 變量,shell 列印一條錯誤訊息,如果不是 交互,以非零狀態退出。
這意味著,如果您正在編寫一個函數,該函數被設計為來源到其他人控制的腳本中,那麼您可能需要對使用未設定的變數持偏執態度 - 否則,如果他們set -u
在調用您的函數之前使用了他們的腳本第一次嘗試擴展未設定的變數時會退出並顯示錯誤訊息。
如果您正在編寫自己的腳本,那麼依靠未設定的變數擴展到空字串並沒有什麼壞處。
編輯- 另外,只是一個想法 - 既然您將整個事情作為條件取決於 terminfo 顏色功能是否可用於您的終端,為什麼不實際使用 terminfo 來產生序列,而不是硬編碼 vt100 值?就像是:
if [ -n "$force_color_prompt" ] && type tput &>/dev/null; then
GREEN="$(tput setaf 2)$(tput bold)"
DIM="$(tput dim)"
RESET="$(tput sgr0)"
fi
這可能會為您帶來一些跨其他終端的可移植性(儘管,不可否認,不使用您顯示的程式碼的終端數量很少並且正在減少)。它也可能失去一些可移植性,因為某些功能可能在某些平台上不存在,這取決於 terminfo 定義的正確程度。 YMMV。
答案2
POSIX 相容的 shell 腳本語言最獨特的功能之一是參數擴充。它可以透過多種方式使用來完成通常不與變數值關聯的任務。在 shell 中,變數不隻隻是一個值,它還可以是一個可操作的項目。它有潛力進行自我測試。這是明確的 - 不需要設定 shell 選項。
例如,您的程式碼可能如下所示:
N= ERR='error encountered - exiting'
: ${force_color_prompt?"$ERR"}
/usr/bin/tput setaf >/dev/null 2>&1 || ${N:?"$ERR"}
: "${GREEN:=\033[1;32m}" "${DIM:=\033[2m}" "${RESET:=\033[00m}"
printf %b\\n \
"Oh, ${GREEN}green${RESET} world, ${DIM}don't desert me now...${RESET}"
此$N
變數被明確地設定為空字串,因此當以${N:?}
參數擴展的形式對其進行求值時,其父 shell 會自動退出,並且後面的語句?
也會進行擴展求值,其結果輸出在 上stderr
。這同樣適用$force_color_prompt
- 如果未設置,則腳本會因錯誤退出並自動輸出$ERR
到stderr
- 。
如果和 目前未設定或設定為空字串,$GREEN
$RESET
則將設定為您定義的值。這使您能夠將它們的值作為環境變數傳遞到腳本中。例如,如果上面的程式碼片段位於名為的腳本中,我這樣稱呼它:$DIM
''
greenworld.sh
GREEN="$(tput setaf 2)$(tput bold)" greenworld.sh
然後$GREEN
不會在腳本的內容中重置,而是繼承我為其設定的明確值。這使得 shell 腳本變得靈活。
正如 godlygeek 所建議的那樣,以這種方式使用tput
是我的建議之一。
在 shell 中,未設定的變數有時與設定的變數一樣有用。這是一個不同的例子:
set -- *
while ${1+:} false ; do
#do stuff until all positionals are shifted away
shift ; done
在這個範例中,只要定義了第一個參數,它就會擴展到 shell 的內建:
null,從而使下列false
呼叫變成無操作。但是,一旦所有位置參數都被shift
刪除,${1}
就不會以這種方式擴展,並且false
會被調用,並且while
循環結束。你可以對此做無數的變化。
答案3
在普通使用中,即當您只是擴展變數時,未設定的變數在所有 Bourne/POSIX 風格的 shell 中被視為空。例外情況是當set -o unset
akaset -u
生效時,在這種情況下,如果您嘗試存取未設定變數的值,shell 將引發錯誤(請參閱神極客的回答)。
有多種方法可以測試變數是否未設定或為空;例如,如果已設置(即使它為空),則構造${foo-bar}
會擴展為值;如果未設置,則構造將擴展為值;(帶有額外的冒號)將空變數視為未設定。其他類似的擴展結構、、的行為類似。除其他差異外,和的輸出中還會出現一個空變數(如果導出的話)。如果您願意,您只會遇到這些差異。foo
bar
foo
${foo:-bar}
${foo+bar}
${foo?bar}
${foo=bar}
set
export
然而,還有一個不同的理由總是初始化變數:你怎麼知道變數確實沒有設定?呼叫者可能為了自己的目的定義了類似的變數。如果呼叫者是同一腳本的其他部分,那麼您在函數中的使用將覆蓋呼叫者的,除非您將變數宣告為本地變數(這僅在ksh/bash/zsh 中可能),因此變數名稱衝突是一個問題反正。但也有可能該變數存在於環境中,因為其他人選擇了與您相同的變數名稱。有一種約定,對 shell 變數使用小寫名稱,對環境變數使用大寫名稱,但它並不能解決所有衝突,也沒有普遍遵循。
$ 匯出 DIM=1 SUM=42 $ 重擊 哦,綠色的世界,1現在不要拋棄我...