如何在 bash 函式中的參數上使用引用呼叫
我正在嘗試將“var name”傳遞給函式,讓函式轉換具有此類“var name”的變數包含的值,然後能夠通過其原始“var name”引用轉換後的對象。
例如,假設我有一個將分隔列表轉換為數組的函式,並且我有一個名為“animal_list”的分隔列表。我想通過將列表名稱傳遞給函式然後將現在的數組引用為“animal_list”來將該列表轉換為數組。
程式碼範例:
function delim_to_array() { local list=$1 local delim=$2 local oifs=$IFS; IFS="$delim"; temp_array=($list); IFS=$oifs; # Now I have the list converted to an array but it's # named temp_array. I want to reference it by its # original name. } # ---------------------------------------------------- animal_list="anaconda, bison, cougar, dingo" delim_to_array ${animal_list} "," # After this point I want to be able to deal with animal_name as an array. for animal in "${animal_list[@]}"; do echo "NAME: $animal" done # And reuse this in several places to converted lists to arrays people_list="alvin|baron|caleb|doug" delim_to_array ${people_list} "|" # Now I want to treat animal_name as an array for person in "${people_list[@]}"; do echo "NAME: $person" done
描述
理解這一點需要一些努力。耐心點。該解決方案將在 bash 中正常工作。需要一些“bashims”。
第一:我們需要使用對變數的“間接”訪問
${!variable}
。如果$variable
包含字元串animal_name
,“參數擴展”:${!variable}
將擴展為$animal_name
.讓我們看看這個想法的實際效果,我盡可能保留了您使用的名稱和值,以便您更容易理解:
#!/bin/bash function delim_to_array() { local VarName=$1 local IFS="$2"; printf "inside IFS=<%s>\n" "$IFS" echo "inside var $VarName" echo "inside list = ${!VarName}" echo a\=\(${!VarName}\) eval a\=\(${!VarName}\) printf "in <%s> " "${a[@]}"; echo eval $VarName\=\(${!VarName}\) } animal_list="anaconda, bison, cougar, dingo" delim_to_array "animal_list" "," printf "out <%s> " "${animal_list[@]}"; echo printf "outside IFS=<%s>\n" "$IFS" # Now we can use animal_name as an array for animal in "${animal_list[@]}"; do echo "NAME: $animal" done
如果執行了完整的腳本(假設它的名稱為 so-setvar.sh),您應該會看到:
$ ./so-setvar.sh inside IFS=<,> inside var animal_list inside list = anaconda, bison, cougar, dingo a=(anaconda bison cougar dingo) in <anaconda> in <bison> in <cougar> in <dingo> out <anaconda> out <bison> out <cougar> out <dingo> outside IFS=< > NAME: anaconda NAME: bison NAME: cougar NAME: dingo
了解“內部”意味著“功能內部”,而“外部”則相反。
裡面的值
$VarName
是 var: 的名稱animal_list
,作為一個字元串。的值
${!VarName}
顯示為列表:anaconda, bison, cougar, dingo
現在,為了展示解決方案是如何建構的,有一行帶有 echo:
echo a\=\(${!VarName}\)
它顯示了以下行的
eval
執行內容:a=(anaconda bison cougar dingo)
一旦被評估,變數
a
就是一個帶有動物列表的數組。在這種情況下,var a 用於準確顯示 eval 如何影響它。然後,每個元素的值
a
列印為<in> val
。並且在函式的外部部分執行相同的操作,如
<out> val
這兩行所示:
in <anaconda> in <bison> in <cougar> in <dingo> out <anaconda> out <bison> out <cougar> out <dingo>
請注意,真正的更改是在函式的最後一個 eval 中執行的。
就這樣,完成了。var 現在有一個值數組。其實函式的核心就是一行:
eval $VarName\=\(${!VarName}\)
此外,IFS 的值被設置為函式的本地值,使其返回到執行函式之前的值,而無需任何額外的工作。感謝Peter Cordes對最初想法的評論。
解釋到此結束,希望清楚。
實函式
如果我們刪除所有不需要的行,只留下核心 eval,只為 IFS 創建一個新變數,我們將函式簡化為它的最小表達式:
delim_to_array() { local IFS="${2:-$' :|'}" eval $1\=\(${!1}\); }
將 IFS 的值設置為局部變數,還允許我們為函式設置“預設”值。每當 IFS 所需的值未作為第二個參數發送到函式時,本地 IFS 將採用“預設”值。我覺得預設值應該是
space
()(這始終是一個有用的分割值)、colon
(:) 和vertical line
(|)。這三個中的任何一個都會拆分值。當然,預設值可以設置為適合您需要的任何其他值。編輯使用
read
:為了降低 eval 中未引用值的風險,我們可以使用:
delim_to_array() { local IFS="${2:-$' :|'}" # eval $1\=\(${!1}\); read -ra "$1" <<<"${!1}" } test="fail-test"; a="fail-test" animal_list='bison, a space, {1..3},~/,${a},$a,$((2+2)),$(echo "fail"),./*,*,*' delim_to_array "animal_list" "," printf "<%s>" "${animal_list[@]}"; echo
$ so-setvar.sh <bison>< a space>< {1..3}><~/><${a}><$a><$((2+2))><$(echo "fail")><./*><*><*>
上面為 var 設置的大多數值
animal_list
都會因 eval 而失敗。但是通過閱讀沒有問題。
- 注意:在此程式碼中嘗試 eval 選項是非常安全的,因為在呼叫函式之前 vars 的值已設置為純文字值。即使真的執行了,它們也只是文本。命名錯誤的文件甚至都不是問題,因為路徑名擴展是最後一次擴展,因此不會在路徑名擴展上重新執行變數擴展。同樣,使用原樣的程式碼,這絕不是對
eval
.例子
為了真正理解這個函式是什麼以及如何工作,我使用這個函式重寫了你發布的程式碼:
#!/bin/bash delim_to_array() { local IFS="${2:-$' :|'}" # printf "inside IFS=<%s>\n" "$IFS" # eval $1\=\(${!1}\); read -ra "$1" <<<"${!1}"; } animal_list="anaconda, bison, cougar, dingo" delim_to_array "animal_list" "," printf "NAME: %s\t " "${animal_list[@]}"; echo people_list="alvin|baron|caleb|doug" delim_to_array "people_list" printf "NAME: %s\t " "${people_list[@]}"; echo
$ ./so-setvar.sh NAME: anaconda NAME: bison NAME: cougar NAME: dingo NAME: alvin NAME: baron NAME: caleb NAME: doug
如您所見,IFS 僅在函式內部設置,不會永久更改,因此不需要將其重新設置為舊值。此外,對函式的第二次呼叫“people_list”利用了 IFS 的預設值,無需設置第二個參數。
警告 01:
在構造 (eval) 函式時,有一個地方 var 未加引號地暴露給 shell 解析。這允許我們使用 IFS 值完成“分詞”。但這也將變數的值(除非某些引用阻止)暴露為:“大括號擴展”、“波浪號擴展”、“參數、變數和算術擴展”、“命令替換”和“路徑名擴展”,在那個命令。並在支持它的系統中進行程序替換
<() >()
。每個(最後一個除外)的範例包含在這個簡單的迴聲中(小心):
a=failed; echo {1..3} ~/ ${a} $a $((2+2)) $(ls) ./*
也就是說,任何以文件名開頭
{~$
<>或可能匹配文件名或包含的字元串
?*[]`都是潛在的問題。如果您確定變數不包含此類有問題的值,那麼您是安全的。如果有可能擁有這樣的價值觀,那麼回答您的問題的方法會更加複雜,並且需要更多(甚至更長)的描述和解釋。使用
read
是另一種選擇。警告 02:
是的,
read
帶有它自己的«龍»份額。
- 始終使用 -r 選項,我很難想到不需要它的條件。
- 該
read
命令只能得到一行。多行,即使通過設置-d
選項,也需要特別小心。或者整個輸入將分配給一個變數。- 如果
IFS
value 包含空格,則將刪除前導和尾隨空格。好吧,完整的描述應該包括一些關於 的細節tab
,但我會跳過它。- 不要
|
通過管道讀取數據。如果你這樣做, read 將在一個子shell中。在子 shell 中設置的所有變數在返回到父 shell 時不會持續存在。好吧,有一些解決方法,但是,我將再次跳過細節。我並不是要包括閱讀的警告和問題,但應大眾的要求,我不得不包括它們,抱歉。