web-dev-qa-db-ja.com

Bashの多次元配列

Linuxシステムの一部を管理するスクリプトを計画しており、 bash または python のどちらを使用するかを決定する段階にあります。

コマンドが簡単であるという理由だけで、これをBashスクリプトとして実行したいのですが、本当の決定要因は構成です。構成ファイルに多次元配列を保存して、それ自体で何をするかをスクリプトに伝える必要があります。単純なkey = valueペアを設定ファイルに保存するのはbashで十分簡単ですが、多次元配列を行うために考えられる唯一の方法は、2層の解析エンジンです。

array=&d1|v1;v2;v3&d2|v1;v2;v3

しかし、マーシャル/アンマーシャルのコードはクマになる可能性があり、これを管理しなければならない次の貧しい樹液にとってユーザーフレンドリーではありません。 bashでこれを簡単に行えない場合は、構成をxmlファイルに書き込み、Pythonでスクリプトを記述します。

これをbashで簡単に行う方法はありますか?

みんな、ありがとう。

44
scphantm

Bashは多次元配列もハッシュもサポートしておらず、値が配列であるハッシュが必要なようです。このソリューションはあまり美しくありません。xmlファイルを使用したソリューションの方が優れているはずです。

array=('d1=(v1 v2 v3)' 'd2=(v1 v2 v3)')
for elt in "${array[@]}";do eval $elt;done
echo "d1 ${#d1[@]} ${d1[@]}"
echo "d2 ${#d2[@]} ${d2[@]}"
43

Bashには多次元配列はありません。ただし、連想配列を使用すると、多少似た効果をシミュレートできます。以下は、多次元配列として使用されるふりをする連想配列の例です。

declare -A arr
arr[0,0]=0
arr[0,1]=1
arr[1,0]=2
arr[1,1]=3
echo "${arr[0,0]} ${arr[0,1]}" # will print 0 1

配列を(-Aで)連想配列として宣言しない場合、上記は機能しません。たとえば、declare -A arr行を省略すると、2 30 1などは算術式として解釈され、0,0(コンマ演算子の右側の値)として評価されるため、echo1,0ではなく0を出力します。

18
Jahid

これは私のために働いたものです。

# Define each array and then add it to the main one
SUB_0=("name0" "value0")
SUB_1=("name1" "value1")
MAIN_ARRAY=(
  SUB_0[@]
  SUB_1[@]
)

# Loop and print it.  Using offset and length to extract values
COUNT=${#MAIN_ARRAY[@]}
for ((i=0; i<$COUNT; i++))
do
  NAME=${!MAIN_ARRAY[i]:0:1}
  VALUE=${!MAIN_ARRAY[i]:1:1}
  echo "NAME ${NAME}"
  echo "VALUE ${VALUE}"
done

この回答はこちら に基づいています

14
Paul Sheldrake

使用されているシェル(sh、ksh、bashなど)に関係なく、次のアプローチはn次元配列に対して非常にうまく機能します(サンプルは2次元配列をカバーしています)。

サンプルでは、​​行区切り文字(1次元)はスペース文字です。フィールド区切り文字(2次元)を導入するには、標準のUNIXツールtrが使用されます。追加のディメンション用の追加のセパレーターも同じ方法で使用できます。

もちろん、このアプローチのパフォーマンスはあまりよくありませんが、パフォーマンスが基準でない場合、このアプローチは非常に一般的であり、多くの問題を解決できます。

array2d="1.1:1.2:1.3 2.1:2.2 3.1:3.2:3.3:3.4"

function process2ndDimension {
    for dimension2 in $*
    do
        echo -n $dimension2 "   "
    done
    echo
}

function process1stDimension {
    for dimension1 in $array2d
    do
        process2ndDimension `echo $dimension1 | tr : " "`
    done
}

process1stDimension

そのサンプルの出力は次のようになります。

1.1     1.2     1.3     
2.1     2.2     
3.1     3.2     3.3     3.4 
8
yaccob

多くの試行錯誤の後、私は実際にbashで最良で最も明確で最も簡単な多次元配列を見つけるのは通常のvarを使用することです。うん。

利点:大きな配列をループする必要はなく、「$ var」をエコーし​​てgrep/awk/sedを使用するだけです。簡単でわかりやすく、好きなだけ列を作成できます。

例:

$ var=$(echo -e 'kris hansen oslo\nthomas jonson peru\nbibi abu johnsonville\njohnny lipp peru')

$ echo "$var"
kris hansen oslo
thomas johnson peru
bibi abu johnsonville
johnny lipp peru

ペルーでみんなを見つけたいなら

$ echo "$var" | grep peru
thomas johnson peru
johnny lipp peru

3番目のフィールドにはgrep(sed)のみ

$ echo "$var" | sed -n -E '/(.+) (.+) peru/p'
thomas johnson peru
johnny lipp peru

Xフィールドのみが必要な場合

$ echo "$var" | awk '{print $2}'
hansen
johnson
abu
johnny

トーマスと呼ばれ、彼の姓を返すペルーの全員

$ echo "$var" |grep peru|grep thomas|awk '{print $2}'
johnson

あなたが考えることができるクエリ...超簡単。

アイテムを変更するには:

$ var=$(echo "$var"|sed "s/thomas/pete/")

「x」を含む行を削除するには

$ var=$(echo "$var"|sed "/thomas/d")

別のアイテムの値に基づいて同じ行の別のフィールドを変更するには

$ var=$(echo "$var"|sed -E "s/(thomas) (.+) (.+)/\1 test \3/")
$ echo "$var"
kris hansen oslo                                                                                                                                             
thomas test peru                                                                                                                                          
bibi abu johnsonville
johnny lipp peru

もちろん、それをしたい場合はループも動作します

$ for i in "$var"; do echo "$i"; done
kris hansen oslo
thomas jonson peru
bibi abu johnsonville
johnny lipp peru

これで見つかった唯一の落とし穴は、must var(例ではvarとiの両方)を常に引用することです

$ for i in "$var"; do echo $i; done
kris hansen oslo thomas jonson peru bibi abu johnsonville johnny lipp peru

入力にスペースが含まれていると機能しないと誰かが間違いなく言うでしょうが、入力に別のデリミターを使用することで修正できます、例えば含まれていますが、任意のofc)を選択できます:

$ var=$(echo -e 'field one☥field two hello☥field three yes moin\nfield 1☥field 2☥field 3 dsdds aq')

$ for i in "$var"; do echo "$i"; done
field one☥field two hello☥field three yes moin
field 1☥field 2☥field 3 dsdds aq

$ echo "$var" | awk -F '☥' '{print $3}'
field three yes moin
field 3 dsdds aq

$ var=$(echo "$var"|sed -E "s/(field one)☥(.+)☥(.+)/\1☥test☥\3/")
$ echo "$var"
field one☥test☥field three yes moin
field 1☥field 2☥field 3 dsdds aq

入力に改行を保存したい場合は、入力前に改行を別のものに変換し、出力で再び変換することができます(またはbashを使用しないでください)。楽しい!

4
n00p

Paulの答えを拡張-これは、bashで連想サブアレイを操作する私のバージョンです。

declare -A SUB_1=(["name1key"]="name1val" ["name2key"]="name2val")
declare -A SUB_2=(["name3key"]="name3val" ["name4key"]="name4val")
STRING_1="string1val"
STRING_2="string2val"
MAIN_ARRAY=(
  "${SUB_1[*]}"
  "${SUB_2[*]}"
  "${STRING_1}"
  "${STRING_2}"
)
echo "COUNT: " ${#MAIN_ARRAY[@]}
for key in ${!MAIN_ARRAY[@]}; do
    IFS=' ' read -a val <<< ${MAIN_ARRAY[$key]}
    echo "VALUE: " ${val[@]}
    if [[ ${#val[@]} -gt 1 ]]; then
        for subkey in ${!val[@]}; do
            subval=${val[$subkey]}
            echo "SUBVALUE: " ${subval}
        done
    fi
done

メイン配列(strings/arrays/assoc)の混合値で動作します。配列

ここで重要なのは、サブ配列を単一引用符で囲み、メイン配列内にサブ配列を格納するときに*の代わりに@を使用して、スペースで区切られた単一の文字列として格納されるようにすることです:"${SUB_1[*]}"

その後、IFS=' ' read -a val <<< ${MAIN_ARRAY[$key]}を使用して値をループ処理するときに、その配列を簡単に解析できます

上記のコードの出力:

COUNT:  4
VALUE:  name1val name2val
SUBVALUE:  name1val
SUBVALUE:  name2val
VALUE:  name4val name3val
SUBVALUE:  name4val
SUBVALUE:  name3val
VALUE:  string1val
VALUE:  string2val
2

Bashの2次元配列の動作を(少なくともある程度)模倣する非常にシンプルで明確な方法であるため、以下を投稿しています。ヒアファイル(Bashマニュアルを参照)とread(Bash組み込みコマンド)を使用します。

## Store the "two-dimensional data" in a file ($$ is just the process ID of the Shell, to make sure the filename is unique)
cat > physicists.$$ <<EOF
Wolfgang Pauli 1900
Werner Heisenberg 1901
Albert Einstein 1879
Niels Bohr 1885
EOF
nbPhysicists=$(wc -l physicists.$$ | cut -sf 1 -d ' ')     # Number of lines of the here-file specifying the physicists.

## Extract the needed data
declare -a person     # Create an indexed array (necessary for the read command).                                                                                 
while read -ra person; do
    firstName=${person[0]}
    familyName=${person[1]}
    birthYear=${person[2]}
    echo "Physicist ${firstName} ${familyName} was born in ${birthYear}"
    # Do whatever you need with data
done < physicists.$$

## Remove the temporary file
rm physicists.$$

出力:Physicist Wolfgang Pauli was born in 1900 Physicist Werner Heisenberg was born in 1901 Physicist Albert Einstein was born in 1879 Physicist Niels Bohr was born in 1885

動作方法:

  • 作成された一時ファイルの行は1次元ベクトルの役割を果たします。空白(または選択した分離文字。Bashマニュアルのreadコマンドの説明を参照)がこれらのベクトルの要素を分離します。 。
  • 次に、-aオプションを指定したreadコマンドを使用して、ファイルの各行をループします(ファイルの終わりに達するまで)。各行について、ループの直前に宣言した配列に目的のフィールド(=単語)を割り当てることができます。ヒアドキュメント-rにバックスラッシュを入力した場合、readコマンドのphysicists.$$オプションは、バックスラッシュがエスケープ文字として機能するのを防ぎます。

結論として、ファイルは2D配列として作成され、その要素は各行のループを使用して抽出され、readコマンドの機能を使用して(インデックス付き)配列の要素に単語を割り当てます。

わずかな改善:

上記のコードでは、ファイルphysicists.$$whileループへの入力として与えられているため、実際にはreadコマンドに渡されます。ただし、whileループ内の入力を求める別のコマンドがある場合、これが問題を引き起こすことがわかりました。たとえば、selectコマンドは標準入力を待機し、whileループ内に配置された場合、コマンドラインでユーザー入力を求める代わりに、physicists.$$から入力を取得します。これを修正するには、read-uオプションを使用します。これにより、ファイル記述子からの読み取りが可能になります。次のコードのように、physicists.$$に対応する(execコマンドを使用して)ファイル記述子を作成し、それを読み取りの-uオプションに指定するだけです。

## Store the "two-dimensional data" in a file ($$ is just the process ID of the Shell, to make sure the filename is unique)
cat > physicists.$$ <<EOF
Wolfgang Pauli 1900
Werner Heisenberg 1901
Albert Einstein 1879
Niels Bohr 1885
EOF
nbPhysicists=$(wc -l physicists.$$ | cut -sf 1 -d ' ')     # Number of lines of the here-file specifying the physicists.
exec {id_nuclei}<./physicists.$$     # Create a file descriptor stored in 'id_nuclei'.

## Extract the needed data
declare -a person     # Create an indexed array (necessary for the read command).                                                                                 
while read -ra person -u "${id_nuclei}"; do
    firstName=${person[0]}
    familyName=${person[1]}
    birthYear=${person[2]}
    echo "Physicist ${firstName} ${familyName} was born in ${birthYear}"
    # Do whatever you need with data
done

## Close the file descriptor
exec {id_nuclei}<&-
## Remove the temporary file
rm physicists.$$

ファイル記述子が最後に閉じられていることに注意してください。

1
Giuseppe

Bashスクリプトを使用して読みやすくする場合は、構造化されたJSONにデータを配置することをお勧めします。次に、bashコマンドで lightweight tool jq を使用して配列を反復処理します。たとえば、次のデータセットの場合:

[

    {"specialId":"123",
    "specialName":"First"},

    {"specialId":"456",
    "specialName":"Second"},

    {"specialId":"789",
    "specialName":"Third"}
]

Bashスクリプトと次のようなjqを使用して、このデータを反復処理できます。

function loopOverArray(){

    jq -c '.[]' testing.json | while read i; do
        # Do stuff here
        echo "$i"
    done
}

loopOverArray

出力:

{"specialId":"123","specialName":"First"}
{"specialId":"456","specialName":"Second"}
{"specialId":"789","specialName":"Third"}
1
Dustin

bash 4であるため連想配列を使用してこれを行い、手動で定義できる値にIFSを設定します。

このアプローチの目的は、連想配列キーの値として配列を持つことです。

IFSをデフォルトに戻すには、設定を解除してください。

  • unset IFS

これは一例です:

#!/bin/bash

set -euo pipefail

# used as value in asscciative array
test=(
  "x3:x4:x5"
)
# associative array
declare -A wow=(
  ["1"]=$test
  ["2"]=$test
)
echo "default IFS"
for w in ${wow[@]}; do
  echo "  $w"
done

IFS=:
echo "IFS=:"
for w in ${wow[@]}; do
  for t in $w; do
    echo "  $t"
  done
done
echo -e "\n or\n"
for w in ${!wow[@]}
do
  echo "  $w"
  for t in ${wow[$w]}
  do
    echo "    $t"
  done
done

unset IFS
unset w
unset t
unset wow
unset test

以下のスクリプトの出力は次のとおりです。

default IFS
  x3:x4:x5
  x3:x4:x5
IFS=:
  x3
  x4
  x5
  x3
  x4
  x5

 or

  1
    x3
    x4
    x5
  2
    x3
    x4
    x5
1
rocksteady

ここでは、bashで多次元配列を作成するための多くの回答があります。

そして例外なく、すべてが鈍くて使いにくいです。

MDアレイが必須の基準である場合は、決定を下すときです。

MDアレイをサポートする言語を使用

私の好みはPerlです。ほとんどの人はおそらくPythonを選択するでしょう。どちらも動作します。

データを他の場所に保存する

JSONとjqはすでに提案されています。 XMLも推奨されていますが、使用する場合はJSONとjqの方が簡単です。

しかし、あなたがする必要があることに対して、Bashが最良の選択ではないかもしれないと思われます。

正しい質問は、「ツールYでXを実行するにはどうすればよいですか?」ではなく、「Xを実行するのに最適なツールはどれですか?」です。

0
Jared Still

Bashは多次元配列をサポートしていませんが、Associate配列を使用して実装できます。ここで、インデックスは値を取得するためのキーです。配列の関連付けは、bashバージョン4で利用可能です。

#!/bin/bash

declare -A arr2d
rows=3
columns=2

for ((i=0;i<rows;i++)) do
    for ((j=0;j<columns;j++)) do
        arr2d[$i,$j]=$i
    done
done


for ((i=0;i<rows;i++)) do
    for ((j=0;j<columns;j++)) do
        echo ${arr2d[$i,$j]}
    done
done
0
rashok

私は非常にシンプルでありながらスマートな回避策を持っています:名前の変数で配列を定義するだけです。例えば:

for (( i=0 ; i<$(($maxvalue + 1)) ; i++ ))
  do
  for (( j=0 ; j<$(($maxargument + 1)) ; j++ ))
    do
    declare -a array$i[$j]=((Your rule))
  done
done

これがあなたの求めていたものではないので、これが役立つかどうかはわかりませんが、私にとってはうまくいきます。 (配列を持たない変数でも同じことが実現できます)

0
testiner