名前に変数を含むbash配列

名前に変数を含むbash配列

次の質問にご協力いただきありがとうございます。

次のように、配列名の一部として変数を含む配列を設定しようとしています。 (ループ数によって変わるArr_$COUNTER位置)$COUNTER

私が試したすべての可能なアプローチでは、「誤った置換」や「予期しないトークンの近くの構文エラー」などのエラーが発生します。

全体のプロセスは次のとおりです。

  1. 複数行を含むファイルがあります。各行には、スペースで区切られた6つの値があります。

    10 20 30 40 50 60  
    100 200 300 400 500 600
    
  2. スクリプトは、ファイルの各行を読み取り、配列として宣言するように設計されています(行番号は変数です)。

  3. テストで各値を印刷する必要があり、最終的には各値に対して異なる機能が実行されます。

    #!/bin/bash
    COUNTER=1
    LINES=`wc -l VALUES_FILE.txt | awk '{print $1}'`
    echo "Total number of lines "$LINES
    echo
    while [ $COUNTER -le $LINES ]
    do
    echo "Counter value is $COUNTER"
    field=`awk "NR == $COUNTER" VALUES_FILE.txt`
    echo "Field = $field"
    declare -a "arr$COUNTER=($field)"
    echo "arr$COUNTER[0] = ${arr$COUNTER[0]}"
    echo "arr$COUNTER[1] = ${arr$COUNTER[1]}"
    echo "arr$COUNTER[2] = ${arr$COUNTER[2]}"
    echo "arr$COUNTER[3] = ${arr$COUNTER[3]}"
    echo "arr$COUNTER[4] = ${arr$COUNTER[4]}"
    echo "arr$COUNTER[5] = ${arr$COUNTER[5]}"
    let COUNTER=COUNTER+1
    echo
    done
    echo "The End"
    echo
    

結果は次のとおりです。

総行数 2

カウンタ値は1です。
フィールド = 10 20 30 40 50 60
./sort.sh: 行 12: arr$COUNTER[0] = ${arr$COUNTER[0]}: 無効な置換
終わる

機能するには何を変更/変更する必要がありますか?

ありがとうございます!

答え1

いくつかの考え:

  1. 変数値の「パラメータ拡張」(${...}部分):

    echo "arr$COUNTER[0] = ${arr$COUNTER[0]}"
    

    動作しません。 evalを使用して問題を解決できます(ただし推奨しません)。

    eval echo "arr$COUNTER[0] = \${arr$COUNTER[0]}"
    

    この行は次のように書くことができます。

    i="arr$COUNTER[0]"; echo "$i = ${!i}"
    

    Bashではこれを間接指定(!)といいます。

  2. 次の行でも同様の問題が発生します。

    declare -a "arr$COUNTER=($field)"
    

    2行に分けてevalを使用する必要があります。

    declare -a "arr$COUNTER"
    eval arr$COUNTER\=\( \$field \)
    

    繰り返しますが、eval(この場合)を使用しないことをお勧めします。

  3. また、ファイル全体をシェルのメモリに読み込んでいる間に、すべての行を配列に配置するより簡単な方法を使用することもできます。

    readarray -t lines <"VALUES_FILE.txt"
    

    これは、各行に対して awk を呼び出すよりも高速です。

上記のすべてを含むスクリプトは次のとおりです。

#!/bin/bash
valfile="VALUES_FILE.txt"

readarray -t lines <"$valfile"             ### read all lines in.

line_count="${#lines[@]}"
echo "Total number of lines $line_count"

for ((l=0;l<$line_count;l++)); do
    echo "Counter value is $l"             ### In which line are we?
    echo "Field = ${lines[l]}"             ### kepth only to help understanding.
    k="arr$l"                              ### Build the variable arr$COUNTER
    IFS=" " read -ra $k <<<"${lines[l]}"   ### Split into an array a line.
    eval max=\${#$k[@]}                    ### How many elements arr$COUNTER has?
    #echo "field $field and k=$k max=$max" ### Un-quote to "see" inside.
    for ((j=0;j<$max;j++)); do             ### for each element in the line.
        i="$k[$j]"; echo "$i = ${!i}"      ### echo it's value.
    done
done
echo "The End"
echo

ただし、AWKで必要な操作を実行できる場合は、AWKが高速になる可能性があります。


awkでも同様の処理が可能です。 6つの値(そのうち4つ)がIPとして使用され、残りの2つが数字とエポック時間であるとします。

非常に単純なAWKスクリプトの例:

#!/bin/sh
valfile="VALUES_FILE.txt"
awk '
NF==6 { printf ( "IP: %s.%s.%s.%s\t",$1,$2,$3,$4)
        printf ( "number: %s\t",$5+2)
        printf ( "epoch: %s\t",$6)
        printf ( "\n" )
    }
' "$valfile"

詳細を含め、新しい質問をしてください。

答え2

変数に名前とインデックスの両方を割り当てる場合は、変数間接参照を使用できます。

s="arr$COUNTER[0]"
echo "arr$COUNTER[0] = ${!s}"

答え3

多次元配列データを1D配列に格納する標準的な方法は、各行を配列のオフセットに格納することです。

要素は、0から始まる行インデックス、が0から始まる列インデックス、および列の数であるインデックス(i,j)にあります。i*m + jijm

また、入力ファイルをインポートし、すべてのスペースを改行に変更しますreadarray

コマンドラインから:

$ readarray -t arr < <( tr -s ' ' '\n' <data )
$ printf '%s\n' "${arr[@]}"
10
20
30
40
50
60
100
200
300
400
500
600

データの列数を数えることができます。

$ m=$( awk '{ print NF; exit }' <data )

行数は次のとおりです。

$ n=$( wc -l <data )

その後、通常どおりダブルループの列と行を繰り返すことができます。

for (( i = 0; i < n; ++i )); do
    for (( j = 0; j < m; ++j )); do
        printf '%4d' "${arr[i*m + j]}"
    done
    printf '\n'
done

与えられたデータに対して以下が生成される。

  10  20  30  40  50  60
 100 200 300 400 500 600

理想的には、Perl、Python、Cなどの多次元配列をサポートする言語を使用する必要があります。つまり、実際にデータセット全体をメモリに保存する必要がありますが、1行ずつ処理できない場合です。

行別処理の場合は、awk代替bashどのこの言語は、あらゆる種類のデータ処理のためにシェルを置き換えるのに最適な候補です。

awk '{ for (i = 1; i <= NF; ++i) printf("%4d", $i); printf("\n") }' data

答え4

eval次のように生成された名前を使用できます。

eval declare -a '"arr'$COUNTER'=($field)"'

デフォルトでは、評価したいメタ文字を除くすべてのメタ文字を引用します。

したがって... $COUNTER1の場合、スクリプトは機能します。

declare -a "arr1=($field)"

追加資料:

関連情報