我有一個獲取 N 個參數的腳本。它首先解析它們(提取特定值X
),然後使用該值呼叫另一個程式:
function main() {
parse "$@"
run "$@"
}
main "$@"
我想在 args 列表的開頭添加一個可選參數(用於設定我正在運行的 python 版本的參數)。所以我將其添加到解析函數中:
if [ "$1" = '2' ] || [ "$1" = '3' ]; then version=$1 && shift; else version=2; fi
但是,解析結束後,我陷入困境,因為它shift
不會影響運行功能。如何在不再次檢查第一個值的值的情況下執行此操作(這將破壞解析函數的全部目的)
答案1
每個 shell 函數的作用域(以及腳本中頂級程式碼的作用域)都有自己的作用域位置參數,並且只能直接存取自己的。最乾淨的解決方案是將您感興趣的位置參數的值放入一個大批。然後,您可以在多個函數作用域中讀取並修改該陣列。
例如,您可以將此程式碼顯示在腳本開頭附近:
declare -a args=("$@")
- 按照您喜歡的方式命名該數組—不必將其稱為
args
。 declare -a
如果你願意的話,你甚至可以省略。- 它實際上不需要出現在開頭附近。它只需要跑步在任何訪問的程式碼
args
運行之前。它甚至可能出現在使用args
.為了清楚起見,我建議將其放在靠近開頭的位置。
然後您可以args
從多個函數對數組進行操作。您不必將其傳遞給函數;他們已經能夠訪問它了。當 shell 函數中的程式碼修改了內容args
然後返回時,呼叫者中的程式碼將能夠觀察到變更。
Bourne 風格的 shell(包括 Bash)中的位置參數使用從 1 開始的索引。 (這是因為$0
,它擴展為程式名稱,從技術上講不是位置參數,並且在函數作用域中不會更改。)但是 Bash 中的陣列使用基於 0 的索引。因此,after args=("$@")
、$1
matches ${args[0]}
、$2
matches ${args[1]}
、$3
matches${args[2]}
等等。$@
仍然符合${args[@]}
您的預期。
為了方便閱讀,我就是這樣寫的,不加引號。當然,您幾乎總是希望對涉及args
數組的擴展使用雙引號,就像您幾乎總是希望對涉及位置參數的擴展使用雙引號一樣。
如果您決定採用這種方法,則可以:
shift
你會寫:
args=("${args[@]:1}")
如果您不熟悉 Bash 中的數組,您需要看一下Bash 參考手冊的相關部分。您可能還想進行互動式實驗。例如:
ek@Cord:~$ args=('foo bar' 'baz quux' 'ham spam')
ek@Cord:~$ printf '[%s]\n' "${args[@]}"
[foo bar]
[baz quux]
[ham spam]
ek@Cord:~$ printf '[%s]\n' "${args[@]:1}"
[baz quux]
[ham spam]
對應的代碼為
if [ "$1" = 2 ] || [ "$1" = 3 ]; then
version="$1"
shift
else
version=2
fi
將會:
if [ "${args[0]}" = 2 ] || [ "${args[0]}" = 3 ]; then
version="${args[0]}"
args=("${args[@]:1}")
else
version=2
fi
使用陣列在語法上更麻煩,但也更靈活。
另一方面,由於您似乎將腳本的大部分程式碼組織成run
函數及其被呼叫者,因此您可能會考慮解析任何特殊命令列參數的替代方案前在任何 shell 函數之外調用run
,然後run "$@"
按照您已經執行的操作進行調用。
有些程式語言的相關文化有一種強烈的道德觀念,就是把幾乎所有東西都放入小型的、獨立的函數中。 Bash 不是這樣一種語言,從 shell 函數傳回複雜資料的方式有限是原因之一。您不應該害怕編寫 shell 函數,甚至應該願意編寫大量微小的 shell 函數。但我認為,如果事實證明您的腳本的最佳形式是其他形式,您不必擔心。
要進一步閱讀,以及一些替代方案,包括一種奇怪的方法,您可以在其中獲取進程替換的輸出,請參閱吉爾斯的回答到函數呼叫者位置參數。