Bash

如何在 bash 函式中的參數上使用引用呼叫

  • January 5, 2016

我正在嘗試將“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 的預設值,無需設置第二個參數。


«這裡是龍» ¯ \ _ (tsu) _ / ¯


警告 01:

在構造 (eval) 函式時,有一個地方 var 未加引號地暴露給 shell 解析。這允許我們使用 IFS 值完成“分詞”。但這也將變數的值(除非某些引用阻止)暴露為:“大括號擴展”、“波浪號擴展”、“參數、變數和算術擴展”、“命令替換”和“路徑名擴展”,在那個命令。並在支持它的系統中進行程序替換<() >()

每個(最後一個除外)的範例包含在這個簡單的迴聲中(小心):

a=failed; echo {1..3} ~/ ${a} $a $((2+2)) $(ls) ./*

也就是說,任何以文件名開頭{~$<>或可能匹配文件名或包含的字元串?*[]`都是潛在的問題。

如果您確定變數不包含此類有問題的值,那麼您是安全的。如果有可能擁有這樣的價值觀,那麼回答您的問題的方法會更加複雜,並且需要更多(甚至更長)的描述和解釋。使用read是另一種選擇。

警告 02:

是的,read帶有它自己的«龍»份額。

  • 始終使用 -r 選項,我很難想到不需要它的條件。
  • read命令只能得到一行。多行,即使通過設置-d選項,也需要特別小心。或者整個輸入將分配給一個變數。
  • 如果IFSvalue 包含空格,則將刪除前導和尾隨空格。好吧,完整的描述應該包括一些關於 的細節tab,但我會跳過它。
  • 不要|通過管道讀取數據。如果你這樣做, read 將在一個子shell中。在子 shell 中設置的所有變數在返回到父 shell 時不會持續存在。好吧,有一些解決方法,但是,我將再次跳過細節。

我並不是要包括閱讀的警告和問題,但應大眾的要求,我不得不包括它們,抱歉。

引用自:https://unix.stackexchange.com/questions/224548