bash配列の要素数をカウントします。配列の名前は動的です(つまり、変数に格納されます)。


11

質問の簡単な説明:

配列の完全なコピーを作成したり、配列を使用したりずに、配列の名前が動的である(つまり、変数に格納されている)bash配列の要素数をカウントする組み込みのbashメソッドはありevalますか?

詳しくは:

bashパラメータ置換を使用すると、次のことができます。

  • 配列の長さを決定します
    myArr=(A B C); echo ${#myArr[@]}
  • 変数を名前で間接的に参照します
    NAME=myVar; echo ${!NAME}
    (これは配列要素にも適用されます):
    NAME=myArr[1]; echo ${!NAME}

しかし、配列の名前が別の変数に格納されている場合、配列内の要素の数をどのように決定できますか?(これは、上記の2つのパラメーター置換の組み合わせと見なすことができます。)例:

myArr=(A B C D)
NAME=myArr
# Get the number of elements in the array indirectly referenced by NAME.
count=${#$NAME[@]}  # This syntax is invalid. What is the right way?

以下は、すべて失敗する複数の試行です。

  # Setup for following attempts:
  myArr=(A B C D)
  NAME=myArr
  EXPR1=$NAME[@]          # i.e. EXPR1='myArr[@]'
  EXPR2=#$NAME[@]         # i.e. EXPR2='#myArr[@]'

  # Failed attempts to get the lengh of the array indirectly:
  1.  count=${#$NAME[@]}  # ERROR: bash: ...: bad substitution
  2.  count=${#!EXPR1}    # ERROR: bash: !EXPR}: event not found
  3.  count=${#\!EXPR1}   # ERROR: bash: ...: bad substitution
  4.  count=${!#EXPR1}    # ERROR: bash: ...: bad substitution
  5.  count=${!EXPR2}     # Returns NULL

上記の他のいくつかのバリアントも試しましたが、(A)配列のコピーを作成するか、(B)を使用して、以下のいずれかなしで機能するものはまだ見つかりませんeval

作業方法:

これを解決するには、おそらく最適ではない方法がいくつかあります(ただし、間違っている場合は修正してください)。

方法1:配列をコピーする

配列を別の(静的な名前の)変数に割り当て、その中の要素の数を取得します。

EXPR=$NAME[@]
arrCopy=( "${!EXPR}" )
count=${#arrCopy}

方法2:使用する eval

EXPR="count=\${#$NAME[@]}"  # i.e. 'count=${myArr[@]}'
eval $EXPR
# Now count is set to the length of the array

概要:

間接的に配列の長さを決定するためのbashの組み込みメソッド(つまり、パラメーター置換構文)はありますか?そうでない場合、これを行う最も効率的な方法は何ですか?eval上記の方法だと思いますが、セキュリティやパフォーマンスの問題はありevalますか?


2
ああ。ネストされた変数。ここでは、ネストされた変数を使用するよりも、どのようなアプローチでも考え直します。ここで実際の問題は何ですか?
muru

1
面白い質問ですね。私があなたに警告する唯一のことは、何かがパフォーマンスの問題を持っているか持っていないと仮定することです。私は非常に厳密なテスト中に非常に大きなbashスクリプトを最適化するために、いくつかのbashビルトインがパフォーマンスの面でひどいことを発見しました。 、変数の展開、実際には、その1行で全体の実行が約10〜20%遅くなりました。タイマー付きの大きなループでメソッドをテストすると、結果に驚くかもしれません。
Lizardx 2015年

2
bash namerefs?declare -n ref=abc; abc=(A B C D); printf '%s\n' "${ref[@]}"
iruvar 2015年

@muru-これは単なる意味論ですが、「ネストされた変数」という用語は、バージョン2より前のbashに関連しています。Bashv2は「間接変数参照」の構文を追加しました。私は、間接的に参照される配列の長さを取得するための特定の構文があるかどうかを尋ねています。私はbashの作者が、要求された有用な手法でなければ、スカラー配列に変数の間接参照を実装する努力をしなかったと思います。 。
drwatsoncode 2015

1
私はベンチマークを少しtime bash -c 'a=(1 a +); c=a; for ((i=0;i<100000;i++)); do eval "echo \${#$c[@]}"; done' > /dev/null行いe=$c[@]; d=("${!e}); echo ${#d[@]}ました:同様に、ループ内でも同様です。評価には、コピーにかかる時間の約90%がかかりました。そして、ギャップは配列とその要素が大きいほど大きくなると思います。
muru 2015

回答:


4

インデックス評価でそのようなものを処理する必要があります。配列にすると、間接変数のインデックスを介して間接的にすることができます。

a=(abc1 def2 ghi3 jkl4 mno5)
r=('a[c=${#a[@]}]' a\[i] a\[@])
for   i in   0 1 2 3 4 5
do    c=
      printf "<%s>\n" "${!r-${!r[i<c?1:2]}}"
      printf "\n\tindex is $i and count is $c\n\n"
done

<abc1>

    index is 0 and count is 5

<def2>

    index is 1 and count is 5

<ghi3>

    index is 2 and count is 5

<jkl4>

    index is 3 and count is 5

<mno5>

    index is 4 and count is 5

<abc1>
<def2>
<ghi3>
<jkl4>
<mno5>

    index is 5 and count is 5

bashのインデックスは0から始まるため、配列オブジェクトの総数は常に、最も高いセットのインデックスよりも1つ多くなるため、次のようになります。

c=
echo "${a[c=${#a[@]}]-this index is unset}" "$c"

this index is unset 5

...パラメータが指定されている場合は、デフォルトの単語に展開されます。

提供されていない場合:

c=
${!r}
echo "$c"

5

...害はありません。

ループでは、$index変数を追跡し、少なくとも$countと同じ大きさかどうかを確認します。小さい場合は、有効なインデックスであるため、$r推論変数をに展開しa[i]ますが、それ以上の場合$rは、$aレイをレイ全体に展開します。

これは関数内にあります:

ref_arr(){
    local    index=-1 count=
    local    ref=(   "$1[ count= \${#$1[@]}  ]"
                     "$1[ index ]"    "$1[ @ ]"
    )  &&    printf  "input array '%s' has '%d' members.\n" \
                     "$1"  "${!ref-${count:?invalid array name: "'$1'"}}"
    while    [ "$((index+=1))" -lt "$count"  ]
    do       printf  "$1[$index]  ==  '%s'\n"  "${!ref[1]}"
    done
}
some_array=(some "dumb
            stuff" 12345\'67890 "" \
          '$(kill my computer)')
ref_arr some_array
ref_arr '$(echo won'\''t work)'

input array 'some_array' has '5' members.
some_array[0]  ==  'some'
some_array[1]  ==  'dumb
                stuff'
some_array[2]  ==  '12345'67890'
some_array[3]  ==  ''
some_array[4]  ==  '$(kill my computer)'
bash: count: invalid array name: '$(echo won't work)'


0

bash 4.3の名前参照は天の恵みです。ただし、これは可能です。

$ myArr=(A B C D)
$ NAME=myArr
$ tmp="${NAME}[@]"
$ copy=( "${!tmp}" )
$ echo "${#copy[@]}"
4

返信ありがとうございます。あなたの答えは、「方法1:配列をコピーする」セクションですでに説明したとおりです。また、質問では、配列の長さは「配列の完全なコピーを作成することなく」決定する必要があることも明確に述べられています。
drwatsoncode 2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.