配列をパラメーターとしてbash関数に渡すにはどうすればよいですか?
注:ここでスタックオーバーフローで回答が見つからなかった後、自分でやや粗雑な解決策を投稿しました。渡される配列は1つだけで、パラメーターリストの最後の要素になります。実際には、それは配列をまったく渡していませんが、called_function()によって配列に再構成されているその要素のリストを渡しますが、私にとってはうまくいきました。誰かがより良い方法を知っている場合は、ここに追加してください。
配列をパラメーターとしてbash関数に渡すにはどうすればよいですか?
注:ここでスタックオーバーフローで回答が見つからなかった後、自分でやや粗雑な解決策を投稿しました。渡される配列は1つだけで、パラメーターリストの最後の要素になります。実際には、それは配列をまったく渡していませんが、called_function()によって配列に再構成されているその要素のリストを渡しますが、私にとってはうまくいきました。誰かがより良い方法を知っている場合は、ここに追加してください。
回答:
次のようなものを使用して、引数として複数の配列を渡すことができます。
takes_ary_as_arg()
{
declare -a argAry1=("${!1}")
echo "${argAry1[@]}"
declare -a argAry2=("${!2}")
echo "${argAry2[@]}"
}
try_with_local_arys()
{
# array variables could have local scope
local descTable=(
"sli4-iread"
"sli4-iwrite"
"sli3-iread"
"sli3-iwrite"
)
local optsTable=(
"--msix --iread"
"--msix --iwrite"
"--msi --iread"
"--msi --iwrite"
)
takes_ary_as_arg descTable[@] optsTable[@]
}
try_with_local_arys
sli4-iread sli4-iwrite sli3-iread sli3-iwrite
--msix --iread --msix --iwrite --msi --iread --msi --iwrite
編集/メモ:(以下のコメントから)
descTable
およびoptsTable
名前として渡され、関数内で展開されます。したがって$
、パラメーターとして指定する場合は必要ありません。descTable
などで定義されている場合でもlocal
機能します。ローカルが呼び出す関数からローカルが見えるためです。!
中には、${!1}
引数1、変数を展開します。declare -a
インデックス付き配列を明示的にするだけで、厳密には必要ありません。注:これは、Stack Overflowで答えを見つけられなかったため、自分で投稿したやや粗雑なソリューションです。渡される配列は1つだけで、パラメーターリストの最後の要素になります。実際には、それは配列をまったく渡していませんが、called_function()によって配列に再構成されているその要素のリストを渡しますが、私にとってはうまくいきました。少し後にケンは彼の解決策を投稿しましたが、私は「歴史的な」参照のためにここに私の物を保管しました。
calling_function()
{
variable="a"
array=( "x", "y", "z" )
called_function "${variable}" "${array[@]}"
}
called_function()
{
local_variable="${1}"
shift
local_array=("${@}")
}
ありがとう、TheBonsaiによって改善されました。
called_function "${#array[@]}" "${array[@]}" "${#array2[@]}" "${array2[@]}"
など...いくつかの明らかな制限はありますが、実際には、他の言語で慣れている方法で言語を機能させようとするよりも、言語がサポートする方法で問題を解決する方が適切です。
Ken Bertelsonソリューションについてコメントし、Jan Hettichに回答します。
関数のtakes_ary_as_arg descTable[@] optsTable[@]
行は以下をtry_with_local_arys()
送信します:
descTable
およびのoptsTable
配列を作成しますtakes_ary_as_arg
。takes_ary_as_arg()
関数はdescTable[@]
and optsTable[@]
を文字列として受け取り、and を意味$1 == descTable[@]
し$2 == optsTable[@]
ます。takes_ary_as_arg()
関数の冒頭で${!parameter}
は、間接参照または二重参照と呼ばれる構文を使用しています。これは、の値を使用する代わり$1
に、の拡張値の値を使用$1
することを意味します。例:
baba=booba
variable=baba
echo ${variable} # baba
echo ${!variable} # booba
同様に$2
。
argAry1=("${!1}")
するargAry1
と、直接書き込むのと同じように=
、展開されたで配列(に続く大括弧)として作成されます。必要とされていません。descTable[@]
argAry1=("${descTable[@]}")
declare
注意:このブラケット形式を使用した配列の初期化は、デフォルトのタブ、改行、およびスペースであるIFS
または内部フィールドセパレータに従って新しい配列を初期化することに言及する価値があります。その場合、表記を使用しているため、各要素はそれ自体が引用されているかのように見えます(とは反対)。[@]
[*]
ではBASH
、ローカル変数スコープは現在の関数であり、そこから呼び出されるすべての子関数です。これは、takes_ary_as_arg()
関数がそれらdescTable[@]
とoptsTable[@]
配列を「認識」し、機能していることを意味します(上記の説明を参照)。
その場合、それらの変数自体を直接見てみませんか?そこに書くのと同じです:
argAry1=("${descTable[@]}")
上記の説明を参照してください。descTable[@]
これは、現在のに従って配列の値をコピーするだけIFS
です。
これは、本質的に、通常通り、価値によるものは何も渡していません。
上記のDennis Williamsonのコメントも強調しておきます。スパース配列(すべてのキーが定義されていない配列-それらに「穴」がある)は期待どおりに機能しません。キーが失われ、配列が「圧縮」されます。
そうは言っても、一般化の価値はあります。関数は、名前を知らなくても配列(またはコピー)を取得できます。
実際のコピーの場合:たとえば、キーにevalを使用できます。
eval local keys=(\${!$1})
次に、それらを使用してループを作成し、コピーを作成します。注:ここで!
は以前の間接/二重評価ではなく、配列のコンテキストでは配列のインデックス(キー)を返します。
descTable
およびoptsTable
文字列(無し[@]
)、我々は、と(参照することによってのように)アレイ自体を使用することができますeval
。配列を受け付けるジェネリック関数の場合。Array1
。次にでArray2
、配列名を渡すと便利です。
ここでの基本的な問題は、配列を設計/実装したbash開発者が本当に手に負えないということです。彼らはそれ${array}
をの短い手だと判断しましたが${array[0]}
、これは悪い間違いでした。特に、それ${array[0]}
が意味を持たないと見なし、配列タイプが連想である場合は空の文字列に評価されます。
配列の割り当てはarray=(value1 ... valueN)
、valueがsyntaxの形式をとるため[subscript]=string
、配列内の特定のインデックスに直接値を割り当てます。これにより、数値インデックスとハッシュインデックスの2種類の配列(bash用語では連想配列と呼ばれます)が存在するようになります。また、数値的にインデックス付けされたスパース配列を作成できるようにします。オフ残し[subscript]=
部分が0の順序インデックスから開始して代入文の各新しい値でインクリメント、数値添字アレイの短い手です。
したがって、配列全体、インデックス、およびすべて${array}
に対して評価する必要があります。代入ステートメントの逆に評価されます。3年目のCS専攻はそれを知っているべきです。その場合、このコードは期待どおりに機能します。
declare -A foo bar
foo=${bar}
次に、配列を値によって関数に渡し、1つの配列を別の配列に割り当てると、シェル構文の残りの指示に従って機能します。しかし、これは正しく行われなかったため、代入演算子=
は配列に対して機能しません。また、配列を値によって関数、サブシェル、または一般的な出力(echo ${array}
)に渡すことができません。
したがって、正しく行われていれば、次の例は、bashでの配列の有用性が大幅に向上する方法を示しています。
simple=(first=one second=2 third=3)
echo ${simple}
結果の出力は次のようになります。
(first=one second=2 third=3)
次に、配列は代入演算子を使用して、値によって関数や他のシェルスクリプトに渡すこともできます。ファイルに出力することで簡単に保存でき、ファイルからスクリプトに簡単にロードできます。
declare -A foo
read foo <file
悲しいかな、他の方法では最上級のbash開発チームに失望させられました。
そのため、配列を関数に渡すには、実際には1つのオプションしかありません。それは、nameref機能を使用することです。
function funky() {
local -n ARR
ARR=$1
echo "indexes: ${!ARR[@]}"
echo "values: ${ARR[@]}"
}
declare -A HASH
HASH=([foo]=bar [zoom]=fast)
funky HASH # notice that I'm just passing the word 'HASH' to the function
次の出力になります。
indexes: foo zoom
values: bar fast
これは参照渡しなので、関数で配列に割り当てることもできます。はい、参照される配列にはグローバルスコープが必要ですが、これはシェルスクリプトであることを考えると、それほど大きな問題ではないはずです。連想または疎インデックス配列を値で関数に渡すには、すべてのインデックスと値を次のような単一の文字列として引数リストに(大きな配列の場合はあまり役に立ちません)スローする必要があります。
funky "${!array[*]}" "${array[*]}"
そして、配列を再構成するために、関数内に一連のコードを記述します。
local -n
、受け入れられた回答よりも優れており、最新のものです。このソリューションは、任意のタイプの変数でも機能します。この回答にリストされている例は、に短縮できますlocal -n ARR=${1}
。ただし、/ の-n
オプションはBashバージョン4.3以降でのみ使用できます。local
declare
funky ARR
:)を渡すcircular name reference
と、基本的に関数がしようとするため、シェルは警告を表示しますlocal -n ARR=ARR
。このトピックについてのよい議論。
DevSolarの答えには、わからない点が1つあります(多分彼にはそうする特定の理由があるかもしれませんが、私は1つ考えることができません)。彼は、要素ごとに位置パラメータから反復的に配列を設定します。
より簡単なアプローチは
called_function()
{
...
# do everything like shown by DevSolar
...
# now get a copy of the positional parameters
local_array=("$@")
...
}
function aecho {
set "$1[$2]"
echo "${!1}"
}
例
$ foo=(dog cat bird)
$ aecho foo 1
cat
複数の配列をパラメーターとして渡す簡単な方法は、文字で区切られた文字列を使用することです。次のようにスクリプトを呼び出すことができます。
./myScript.sh "value1;value2;value3" "somethingElse" "value4;value5" "anotherOne"
その後、次のようにコードで抽出できます。
myArray=$1
IFS=';' read -a myArray <<< "$myArray"
myOtherArray=$3
IFS=';' read -a myOtherArray <<< "$myOtherArray"
このようにして、実際に複数の配列をパラメーターとして渡すことができ、最後のパラメーターである必要はありません。
これはスペースでも機能します:
format="\t%2s - %s\n"
function doAction
{
local_array=("$@")
for (( i = 0 ; i < ${#local_array[@]} ; i++ ))
do
printf "${format}" $i "${local_array[$i]}"
done
echo -n "Choose: "
option=""
read -n1 option
echo ${local_array[option]}
return
}
#the call:
doAction "${tools[@]}"
いくつかのトリックを使用して、配列と共に名前付きパラメーターを関数に実際に渡すことができます。
私が開発したメソッドを使用すると、次のような関数に渡されるパラメーターにアクセスできます。
testPassingParams() {
@var hello
l=4 @array anArrayWithFourElements
l=2 @array anotherArrayWithTwo
@var anotherSingle
@reference table # references only work in bash >=4.3
@params anArrayOfVariedSize
test "$hello" = "$1" && echo correct
#
test "${anArrayWithFourElements[0]}" = "$2" && echo correct
test "${anArrayWithFourElements[1]}" = "$3" && echo correct
test "${anArrayWithFourElements[2]}" = "$4" && echo correct
# etc...
#
test "${anotherArrayWithTwo[0]}" = "$6" && echo correct
test "${anotherArrayWithTwo[1]}" = "$7" && echo correct
#
test "$anotherSingle" = "$8" && echo correct
#
test "${table[test]}" = "works"
table[inside]="adding a new value"
#
# I'm using * just in this example:
test "${anArrayOfVariedSize[*]}" = "${*:10}" && echo correct
}
fourElements=( a1 a2 "a3 with spaces" a4 )
twoElements=( b1 b2 )
declare -A assocArray
assocArray[test]="works"
testPassingParams "first" "${fourElements[@]}" "${twoElements[@]}" "single with spaces" assocArray "and more... " "even more..."
test "${assocArray[inside]}" = "adding a new value"
言い換えると、名前でパラメーターを呼び出すことができる(より読みやすいコアを構成する)だけでなく、実際に配列(および変数への参照-この機能はbash 4.3でのみ機能します)を渡すことができます!さらに、マップされた変数はすべて$ 1(およびその他)と同じようにローカルスコープ内にあります。
これを機能させるコードはかなり軽量で、bash 3とbash 4の両方で機能します(これらは私がテストした唯一のバージョンです)。このようなbashでの開発をより簡単に簡単にするトリックに興味がある場合は、私のBash Infinity Frameworkをご覧ください。以下のコードはその目的のために開発されました。
Function.AssignParamLocally() {
local commandWithArgs=( $1 )
local command="${commandWithArgs[0]}"
shift
if [[ "$command" == "trap" || "$command" == "l="* || "$command" == "_type="* ]]
then
paramNo+=-1
return 0
fi
if [[ "$command" != "local" ]]
then
assignNormalCodeStarted=true
fi
local varDeclaration="${commandWithArgs[1]}"
if [[ $varDeclaration == '-n' ]]
then
varDeclaration="${commandWithArgs[2]}"
fi
local varName="${varDeclaration%%=*}"
# var value is only important if making an object later on from it
local varValue="${varDeclaration#*=}"
if [[ ! -z $assignVarType ]]
then
local previousParamNo=$(expr $paramNo - 1)
if [[ "$assignVarType" == "array" ]]
then
# passing array:
execute="$assignVarName=( \"\${@:$previousParamNo:$assignArrLength}\" )"
eval "$execute"
paramNo+=$(expr $assignArrLength - 1)
unset assignArrLength
elif [[ "$assignVarType" == "params" ]]
then
execute="$assignVarName=( \"\${@:$previousParamNo}\" )"
eval "$execute"
elif [[ "$assignVarType" == "reference" ]]
then
execute="$assignVarName=\"\$$previousParamNo\""
eval "$execute"
elif [[ ! -z "${!previousParamNo}" ]]
then
execute="$assignVarName=\"\$$previousParamNo\""
eval "$execute"
fi
fi
assignVarType="$__capture_type"
assignVarName="$varName"
assignArrLength="$__capture_arrLength"
}
Function.CaptureParams() {
__capture_type="$_type"
__capture_arrLength="$l"
}
alias @trapAssign='Function.CaptureParams; trap "declare -i \"paramNo+=1\"; Function.AssignParamLocally \"\$BASH_COMMAND\" \"\$@\"; [[ \$assignNormalCodeStarted = true ]] && trap - DEBUG && unset assignVarType && unset assignVarName && unset assignNormalCodeStarted && unset paramNo" DEBUG; '
alias @param='@trapAssign local'
alias @reference='_type=reference @trapAssign local -n'
alias @var='_type=var @param'
alias @params='_type=params @param'
alias @array='_type=array @param'
受け入れられた回答に追加するだけですが、配列の内容が次のようなものである場合はうまくいきません:
RUN_COMMANDS=(
"command1 param1... paramN"
"command2 param1... paramN"
)
この場合、配列の各メンバーは分割されるため、関数が参照する配列は次と同等です。
RUN_COMMANDS=(
"command1"
"param1"
...
"command2"
...
)
このケースを機能させるには、変数名を関数に渡し、evalを使用する方法を見つけました。
function () {
eval 'COMMANDS=( "${'"$1"'[@]}" )'
for COMMAND in "${COMMANDS[@]}"; do
echo $COMMAND
done
}
function RUN_COMMANDS
ちょうど私の2©
醜いのと同じように、明示的に配列を渡さない限り機能する回避策がありますが、配列に対応する変数は次のとおりです。
function passarray()
{
eval array_internally=("$(echo '${'$1'[@]}')")
# access array now via array_internally
echo "${array_internally[@]}"
#...
}
array=(0 1 2 3 4 5)
passarray array # echo's (0 1 2 3 4 5) as expected
誰かがより明確なアイデアの実装を思い付くと確信していますが、これは配列を渡して"{array[@]"}
を使用して内部的にアクセスするよりも良い解決策であることがわかりましたarray_inside=("$@")
。他の位置/ getopts
パラメータがある場合、これは複雑になります。これらの場合、私は最初にいくつかの組み合わせを使用して配列に関連付けられていないパラメータを決定して削除する必要がありましたshift
配列要素の削除を必要がありました。
純粋主義的な見方では、このアプローチを言語の違反と見なしている可能性がありますが、実用的に言えば、このアプローチによって私は多くの悲しみを救いました。関連トピックではeval
、内部的に構築された配列target_varname
を、関数に渡すパラメーターに従って名前が付けられた変数に割り当てるためにも使用します。
eval $target_varname=$"(${array_inside[@]})"
これが誰かを助けることを願っています。
要件:配列内の文字列を検索する関数。
これは、渡された引数をコピーするのではなく使用するという点で、DevSolarのソリューションを少し簡略化したものです。
myarray=('foobar' 'foxbat')
function isInArray() {
local item=$1
shift
for one in $@; do
if [ $one = $item ]; then
return 0 # found
fi
done
return 1 # not found
}
var='foobar'
if isInArray $var ${myarray[@]}; then
echo "$var found in array"
else
echo "$var not found in array"
fi
私の短い答えは:
function display_two_array {
local arr1=$1
local arr2=$2
for i in $arr1
do
"arrary1: $i"
done
for i in $arr2
do
"arrary2: $i"
done
}
test_array=(1 2 3 4 5)
test_array2=(7 8 9 10 11)
display_two_array "${test_array[*]}" "${test_array2[*]}"
${test_array[*]}
とは、${test_array2[*]}
そうでなければ、失敗するだろう、「」で囲む必要があります。