配列をbashのパラメーターとして渡す


188

配列をパラメーターとしてbash関数に渡すにはどうすればよいですか?

注:ここでスタックオーバーフローで回答が見つからなかった後、自分でやや粗雑な解決策を投稿しました。渡される配列は1つだけで、パラメーターリストの最後の要素になります。実際には、それは配列をまったく渡していませんが、called_function()によって配列に再構成されているその要素のリストを渡しますが、私にとってはうまくいきました。誰かがより良い方法を知っている場合は、ここに追加してください。


1
ここには、素晴らしいリファレンスとたくさんの例があります。
Artem Barger

16
えっと…同じ一分以内に5年前の質問に3つの反対票が?
DevSolar 2014

回答:


220

次のようなものを使用して、引数として複数の配列を渡すことができます

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 インデックス付き配列を明示的にするだけで、厳密には必要ありません。

14
注意すべきことの1つは、元の配列がスパースである場合、受信関数の配列は同じインデックスを持たないということです。
追って通知があるまで一時停止。

13
これは素晴らしいですが、ケンまたは誰かがそれがなぜ機能するのかについて私を困惑させるいくつかのことを説明できます:1-関数の引数として渡されるとき、descTableとoptsTableの前に$を付ける必要があると思いました。2-「takes ...」の最初の行で、明示的な配列宣言が必要なのはなぜですか?3-とは何ですか!式$ {!1}で意味し、なぜ[@]が必要ない、または許可されないのですか?-これは機能し、私のテストに基づいてこれらの詳細のすべてが必要であるようですが、理由を理解したいと思います!
Jan Hettich、2010年

8
1:descTableとoptsTableは名前として渡されるだけなので、$はなく、呼び出された関数内でのみ展開されます2:完全に定かではありませんが、本当に必要ではないと思います3:!関数に渡されるパラメーターを2回展開する必要があるため、$ 1が「descTable [@]」に展開され、それが「$ {descTable [@]}」に展開される必要があるためです。$ {!1}構文はこれを行います。
Elmar Zander

8
「declare -a」の部分は必要ないと思います。括弧の存在は、割り当てのLHSを配列としてすでに定義しています。
Erik Aronesty 2013年

3
この答えは、今問題を解決するのに役立ちました。ただし、私のマシン(bash 4.3.42を使用)では、「$ {!1}」と「$ {!2}」で引用符を削除する必要があることを指摘しておきます。そうしないと、元の配列の値が1つの文字列として読み取られ、それぞれargAry1 [0]とargAry2 [0]に割り当てられます。つまり、基本的に配列構造が失われます。
user.friendly 2016年

85

注:これは、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によって改善されました。


19
事実の3年後、この回答は-歴史的な理由のみのために-数日以内に2つの反対票を獲得しました。悲しいことに、SOでいつものように、なぜこれが正当であると人々が考えるのかについての注記はありません。この回答他のすべての回答よりも古いことに注意してください。私はケンの回答を最善の解決策として受け入れました。私はそれが完璧に近いものではないことを完全に認識していますが、4 か月間、それはSOで利用可能な最高のものでした。ケンの完璧なソリューションの2位になってから2 後に反対票を投じるべき理由は、私を超えています。
DevSolar

@geirha:誰が質問を投稿したか、誰がこの回答を投稿したか、そしておそらくあなたが「悪い」と呼んでいる回答をだれが受け入れたかを確認するようにお願いします。;-) 質問のNoteを確認することもできます。これは、このソリューションがKenのソリューションよりも劣っている理由を示しています。
DevSolar 2014

2
あなたが質問をし、あなたがこの答えを書き、あなたが悪い答えを受け入れたことを私は知っています。だからそう言ったのです。受け入れられた答えが悪いのは、参照によって配列を渡そうとしているからです。これは、あなたが本当に避けるべきことです。さらに、この例では、複数の引数を1つの文字列にまとめています。参照によって配列を本当に渡す必要がある場合、bashはそもそも間違った言語です。bash 4.3の新しいnameref変数を使用しても、名前の衝突(循環参照)を安全に回避することはできません。
geirha 2014

4
まあ、各配列の要素の数を含めれば、複数の配列を渡すことができます。called_function "${#array[@]}" "${array[@]}" "${#array2[@]}" "${array2[@]}"など...いくつかの明らかな制限はありますが、実際には、他の言語で慣れている方法で言語を機能させようとするよりも、言語がサポートする方法で問題を解決する方が適切です。
geirha 2014

1
@geirha:まあ、私たちは同意しないことに同意する必要があると思います。あなたは私が私の質問に最もよく答える答えの裁判官になるようにさせなければなりません。個人的には、とにかく配列を参照渡しすることを好みます(データのコピーを保存するために、言語に関係なく)。代替案が後方に曲がり、配列サイズを追加パラメーターとして渡す場合はさらにそうです...
DevSolar

38

Ken Bertelsonソリューションについてコメントし、Jan Hettichに回答します。

使い方

関数のtakes_ary_as_arg descTable[@] optsTable[@]行は以下をtry_with_local_arys()送信します:

  1. これは実際には、関数にアクセス可能なdescTableおよびのoptsTable配列を作成しますtakes_ary_as_arg
  2. takes_ary_as_arg()関数はdescTable[@]and optsTable[@]を文字列として受け取り、and を意味$1 == descTable[@]$2 == optsTable[@]ます。
  3. takes_ary_as_arg()関数の冒頭で${!parameter}は、間接参照または二重参照と呼ばれる構文を使用しています。これは、の値を使用する代わり$1に、の拡張値の値を使用$1すること意味ます。例:

    baba=booba
    variable=baba
    echo ${variable} # baba
    echo ${!variable} # booba

    同様に$2

  4. これを配置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。配列を受け付けるジェネリック関数の場合。

2
ケン・バーテルソンの説明の背後にあるメカニズムの良い説明。「その場合、これらの変数自体を直接見てみませんか?」という質問に対して、私は答えます。単に関数を再利用するためです。で関数を呼び出す必要があるとしましょうArray1。次にでArray2、配列名を渡すと便利です。
gfrigon 2014年

すばらしい回答です。このような説明が必要です。
エドゥアールロペス

22

ここでの基本的な問題は、配列を設計/実装した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[*]}"

そして、配列を再構成するために、関数内に一連のコードを記述します。


1
使用の解決策はlocal -n、受け入れられた回答よりも優れており、最新のものです。このソリューションは、任意のタイプの変数でも機能します。この回答にリストされている例は、に短縮できますlocal -n ARR=${1}。ただし、/ の-nオプションはBashバージョン4.3以降でのみ使用できます。localdeclare
richardjsimkins 2016年

これはいいね!小さな落とし穴:関数のローカル引数と同じ名前の変数(例funky ARR:)を渡すcircular name referenceと、基本的に関数がしようとするため、シェルは警告を表示しますlocal -n ARR=ARR。このトピックについてのよい議論
ジーンパブロフスキー2016

5

DevSolarの答えには、わからない点が1つあります(多分彼にはそうする特定の理由があるかもしれませんが、私は1つ考えることができません)。彼は、要素ごとに位置パラメータから反復的に配列を設定します。

より簡単なアプローチは

called_function()
{
  ...
  # do everything like shown by DevSolar
  ...

  # now get a copy of the positional parameters
  local_array=("$@")
  ...
}

1
そうしない理由は、数日前まではbash配列をいじらないからです。以前は、複雑になった場合はPerlに切り替えていましたが、現在の仕事にはないオプションです。ヒントをありがとう!
DevSolar 2009年


3

複数の配列をパラメーターとして渡す簡単な方法は、文字で区切られた文字列を使用することです。次のようにスクリプトを呼び出すことができます。

./myScript.sh "value1;value2;value3" "somethingElse" "value4;value5" "anotherOne"

その後、次のようにコードで抽出できます。

myArray=$1
IFS=';' read -a myArray <<< "$myArray"

myOtherArray=$3
IFS=';' read -a myOtherArray <<< "$myOtherArray"

このようにして、実際に複数の配列をパラメーターとして渡すことができ、最後のパラメーターである必要はありません。


1

これはスペースでも機能します:

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[@]}"

2
ここで何がポイントなのかしら。これは通常の引数渡しです。"$ @"構文はスペースで機能するように作成されています。 "$ @"は "$ 1" "$ 2"と同じです...
Andreas Spindler

関数に2つの配列を渡すことはできますか?
pihentagy 2013

1

いくつかのトリックを使用して、配列と共に名前付きパラメーターを関数に実際に渡すことができます。

私が開発したメソッドを使用すると、次のような関数に渡されるパラメーターにアクセスできます。

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'

1

受け入れられた回答に追加するだけですが、配列の内容が次のようなものである場合はうまくいきません:

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©


1

醜いのと同じように、明示的に配列を渡さない限り機能する回避策がありますが、配列に対応する変数は次のとおりです。

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[@]})"

これが誰かを助けることを願っています。


0

要件:配列内の文字列を検索する関数。
これは、渡された引数をコピーするのではなく使用するという点で、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 

0

私の短い答えは:

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[*]}そうでなければ、失敗するだろう、「」で囲む必要があります。


あなたの例は不完全なので間違っています。スクリプトの完全なコードを入力してください。
Dennis VR
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.