bash - associative array（連想配列）

### bash array

bash では、他のプログラミング言語と同様に array（配列）を使用することが出来ます。ここでは、RHEL8 を例に取り、代表的な associative array（連想配列）の操作方法をご紹介します。
```shell
$ cat /etc/redhat-release
Red Hat Enterprise Linux release 8.8 (Ootpa)

$ bash --version
GNU bash, version 4.4.20(1)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
```

#### Create & Manipulate associative array
以下のような方法で associative array を作成することが出来ます。
```shell
$ declare -A ASSO_A=([a]="aaa" [b b]="bbb" [c@+c]="ccc")
$ declare -p ASSO_A
declare -A ASSO_A=([c@+c]="ccc" [a]="aaa" ["b b"]="bbb" )
```
代表的な配列要素（element）の操作には、以下があります。
```shell
# Number of elements
$ echo ${#ASSO_A[@]}
3

# Index List
$ echo "${!ASSO_A[@]}"
c@+c a b b

$ for I in "${!ASSO_A[@]}"; do echo "$I : ${ASSO_A[$I]}"; done
c@+c : ccc
a : aaa
b b : bbb

# Get All Elements
$ echo ${ASSO_A[@]}
ccc aaa bbb
$ echo ${ASSO_A[*]}
ccc aaa bbb

# Get Specific Elements
$ echo ${ASSO_A[c@+c]}
ccc
$ echo ${ASSO_A[b b]}
bbb
```
Slice 操作ではエラーが発生しませんが、期待した結果にならない場合があります。
```shell
# Get Elements with Slice #1
$ echo ${ASSO_A[@]:-1}
ccc aaa bbb
$ echo ${ASSO_A[@]:0}
ccc aaa bbb
$ echo ${ASSO_A[@]:1}
ccc aaa bbb
$ echo ${ASSO_A[@]:2}
aaa bbb
$ echo ${ASSO_A[@]:3}
bbb
$ echo ${ASSO_A[@]:4}


# Get Elements with Slice #2
$ echo ${ASSO_A[@]:-1:1}
ccc aaa bbb
$ echo ${ASSO_A[@]:0:1}
ccc
$ echo ${ASSO_A[@]:1:1}
ccc
$ echo ${ASSO_A[@]:2:1}
aaa
$ echo ${ASSO_A[@]:3:1}
bbb
$ echo ${ASSO_A[@]:4:1}

```
Element の追加・削除等も可能です。
```shell
# Add Elements
$ ASSO_A[z zz z]="Additional Element"
$ declare -p ASSO_A
declare -A ASSO_A=(["z zz z"]="Additional Element" [c@+c]="ccc" [a]="aaa" ["b b"]="bbb" )
$ echo ${!ASSO_A[@]}
z zz z c@+c a b b

# Delete Elements
$ unset ASSO_A["b b"]
$ declare -p ASSO_A
declare -A ASSO_A=(["z zz z"]="Additional Element" [c@+c]="ccc" [a]="aaa" )
$ echo ${!ASSO_A[@]}
z zz z c@+c a
```
ファイルからの入力でも associative array を作成することが出来ます。以下のファイルを使用してみます。
```shell
$ cat associative_input.txt
[A]="aa"
[B B]="bb"
[C Cc C]="cc"
[D-.@]="dd"
```
```shell-session
$ declare -A ASSO_B
$ eval ASSO_B=( $(cat associative_input.txt) )
$ declare -p ASSO_B
declare -A ASSO_B=([D-.@]="dd" ["C Cc C"]="cc" [A]="aa" ["B B"]="bb" )

$ for I in "${!ASSO_B[@]}"; do echo "$I : ${ASSO_B[$I]}"; done
D-.@ : dd
C Cc C : cc
A : aa
B B : bb
```
コマンド出力からでも associative array を作成することが出来ます。以下のコマンドを使用してみます。
```shell
$ systemctl list-timers
NEXT                         LEFT          LAST                         PASSED       UNIT                         ACTIVATES
Tue 2023-11-14 21:10:00 JST  6min left     Tue 2023-11-14 21:00:17 JST  2min 56s ago sysstat-collect.timer        sysstat-collect.service
Wed 2023-11-15 00:00:00 JST  2h 56min left Tue 2023-11-14 09:05:39 JST  11h ago      mlocate-updatedb.timer       mlocate-updatedb.service
Wed 2023-11-15 00:00:00 JST  2h 56min left Tue 2023-11-14 09:05:39 JST  11h ago      unbound-anchor.timer         unbound-anchor.service
Wed 2023-11-15 00:07:00 JST  3h 3min left  n/a                          n/a          sysstat-summary.timer        sysstat-summary.service
Wed 2023-11-15 18:01:58 JST  20h left      Tue 2023-11-14 18:01:58 JST  3h 1min ago  systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service

5 timers listed.
Pass --all to see loaded but inactive timers, too.
```
Timer Unit (UNIT) を Key に、次回時刻 (NEXT) を Value にして作成してみます。
associative array 用に awk で文字列を加工します。
```shell
$ systemctl --no-legend list-timers | awk '{print "["$(NF-1)"]=\""$1,$2,$3,$4"\""}'
[sysstat-collect.timer]="Tue 2023-11-14 21:10:00 JST"
[mlocate-updatedb.timer]="Wed 2023-11-15 00:00:00 JST"
[unbound-anchor.timer]="Wed 2023-11-15 00:00:00 JST"
[sysstat-summary.timer]="Wed 2023-11-15 00:07:00 JST"
[systemd-tmpfiles-clean.timer]="Wed 2023-11-15 18:01:58 JST"
```
上記を元に associative array を作成することができます。
```shell
$ declare -A ASSO_C
$ eval ASSO_C=( $(systemctl --no-legend list-timers | awk '{print "["$(NF-1)"]=\""$1,$2,$3,$4"\""}') )
$ declare -p ASSO_C
declare -A ASSO_C=([systemd-tmpfiles-clean.timer]="Wed 2023-11-15 18:01:58 JST" [unbound-anchor.timer]="Wed 2023-11-15 00:00:00 JST" [mlocate-updatedb.timer]="Wed 2023-11-15 00:00:00 JST" [sysstat-collect.timer]="Tue 2023-11-14 21:10:00 JST" [sysstat-summary.timer]="Wed 2023-11-15 00:07:00 JST" )

$ for I in "${!ASSO_C[@]}"; do echo "$I : ${ASSO_C[$I]}"; done
systemd-tmpfiles-clean.timer : Wed 2023-11-15 18:01:58 JST
unbound-anchor.timer : Wed 2023-11-15 00:00:00 JST
mlocate-updatedb.timer : Wed 2023-11-15 00:00:00 JST
sysstat-collect.timer : Tue 2023-11-14 21:10:00 JST
sysstat-summary.timer : Wed 2023-11-15 00:07:00 JST
```