Linuxシステムの一部を管理するスクリプトを計画しており、 bash または python のどちらを使用するかを決定する段階にあります。
コマンドが簡単であるという理由だけで、これをBashスクリプトとして実行したいのですが、本当の決定要因は構成です。構成ファイルに多次元配列を保存して、それ自体で何をするかをスクリプトに伝える必要があります。単純なkey = valueペアを設定ファイルに保存するのはbashで十分簡単ですが、多次元配列を行うために考えられる唯一の方法は、2層の解析エンジンです。
array=&d1|v1;v2;v3&d2|v1;v2;v3
しかし、マーシャル/アンマーシャルのコードはクマになる可能性があり、これを管理しなければならない次の貧しい樹液にとってユーザーフレンドリーではありません。 bashでこれを簡単に行えない場合は、構成をxmlファイルに書き込み、Pythonでスクリプトを記述します。
これをbashで簡単に行う方法はありますか?
みんな、ありがとう。
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[@]}"
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 3
、0 1
などは算術式として解釈され、0,0
(コンマ演算子の右側の値)として評価されるため、echo
は1,0
ではなく0
を出力します。
これは私のために働いたものです。
# 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
この回答はこちら に基づいています
使用されているシェル(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
多くの試行錯誤の後、私は実際に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
$ echo "$var" | sed -n -E '/(.+) (.+) peru/p'
thomas johnson peru
johnny lipp peru
$ 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/")
$ 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を使用しないでください)。楽しい!
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
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
動作方法:
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.$$
ファイル記述子が最後に閉じられていることに注意してください。
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"}
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
ここでは、bashで多次元配列を作成するための多くの回答があります。
そして例外なく、すべてが鈍くて使いにくいです。
MDアレイが必須の基準である場合は、決定を下すときです。
MDアレイをサポートする言語を使用
私の好みはPerlです。ほとんどの人はおそらくPythonを選択するでしょう。どちらも動作します。
データを他の場所に保存する
JSONとjqはすでに提案されています。 XMLも推奨されていますが、使用する場合はJSONとjqの方が簡単です。
しかし、あなたがする必要があることに対して、Bashが最良の選択ではないかもしれないと思われます。
正しい質問は、「ツールYでXを実行するにはどうすればよいですか?」ではなく、「Xを実行するのに最適なツールはどれですか?」です。
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
私は非常にシンプルでありながらスマートな回避策を持っています:名前の変数で配列を定義するだけです。例えば:
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
これがあなたの求めていたものではないので、これが役立つかどうかはわかりませんが、私にとってはうまくいきます。 (配列を持たない変数でも同じことが実現できます)