如何移動參數並將其完全從 args ls 中拋出?

如何移動參數並將其完全從 args ls 中拋出?

我有一個獲取 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=("$@")$1matches ${args[0]}$2matches ${args[1]}$3matches${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 函數。但我認為,如果事實證明您的腳本的最佳形式是其他形式,您不必擔心。

要進一步閱讀,以及一些替代方案,包括一種奇怪的方法,您可以在其中獲取進程替換的輸出,請參閱吉爾斯的回答函數呼叫者位置參數

相關內容