Bashのビルトインコマンド”declare”の使い方紹介(その1)

2023-03-27Bash,CentOS,Cygwin,Linux,Ubuntu

シェルスクリプトサンプルコードでよく見かけるtypesetやdeclareってなに? の続きです。
( declaretypedef について知りたい方は前回のエントリのほうが参考になるかもしれません。 )

Bash のビルトインコマンドである declare を使って変数を定義できますが、 オプションを指定することで真価を発揮します
declareオプションを見ていきましょう。

オプションは3つに分類できます。(僕が勝手に分類した)

  1. 変数の型を定義するもの
  2. 変数の利用を制限・開放するもの
  3. その他

検証環境

$ uname -moi
x86_64 x86_64 GNU/Linux

$ bash -version | head -n 1
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)

declareオプション(変数の型を定義するもの)

-A オプション

連想配列として変数を定義できます。

# 連想配列を定義
$ declare -A MAP

# 要素を代入
$ MAP['a']=11
$ MAP['b']=22
$ MAP['c']=33

# 単純にechoしても何も出力されない
$ echo $MAP

# 添字を指定すればOK
$ echo ${MAP[a]}
11
$ echo ${MAP["a"]}
11

# カッコが足りないと正しく出力されない(T_T)
$ echo $MAP[a]
[a]
$ echo $MAP["a"]
[a]

# ちょっと特殊な書き方をしてやると出力できるが、値の一覧だけしか出力されない
$ echo "${MAP[@]}"
11 22 33

# "!" をつけてやると連想配列のキーも取得できます。(ここまで来るといよいよ意味不明。呪文じみてくる)
# ただ、この方法を使えばキーでループさせることが可能。
$ echo "${!MAP[@]}"
a b c

# -p オプションを使うとデバッグしやすい(後述)
$ declare -p MAP
declare -A MAP=([c]="33" [b]="22" [a]="11" )

ちなみに、変数の定義タイミングで値を代入するリテラル記述も存在します。

# 定義と同時に初期化
$ declare -A MAP=([i]=わたしは [my]=わたしの [me]=わたしを)

$ echo ${MAP[my]}
わたしの

# 上書きできてしまう
$ declare -A MAP=([i]=わたしは [my]=わたしはわたし [me]=わたしを)

$ echo ${MAP[my]}
わたしはわたし

-a オプション

配列として変数を定義できます。
添字には数字が使われる。

# 配列を定義
$ declare -a ARRAY

# 要素を代入
$ ARRAY[0]=11
$ ARRAY[1]=22

# 追加する場合
$ ARRAY+=(33)

# 単純にechoすると最初の要素だけしか出力されない
$ echo $ARRAY
11

# 添字を指定すればOK
$ echo ${ARRAY[1]}
22
$ echo ${ARRAY["1"]}
22

# カッコが足りないと正しく出力されない(T_T)
$ echo $ARRAY[1]
11[1]
$ echo $ARRAY["1"]
11[1]

# ちょっと特殊な書き方をしてやると出力できるが、値の一覧だけしか出力されない
$ echo "${ARRAY[@]}"
11 22 33

# "!" をつけてやるとインデックスも取得できます。
$ echo "${!ARRAY[@]}"
0 1 2

# -p オプションを使うとデバッグしやすい(後述)
$ declare -p ARRAY
declare -a ARRAY='([0]="11" [1]="22" [2]="33")'

こうやって見ると、連想配列変数と配列変数にさほど違いがないように見えます。少なくとも変数の参照方式には差異がないです。

強いて言えば、配列変数を添え字無しで echo した場合は最初の要素が出力されるということぐらいです。

実は両者は同じもので、 添え字無しで echo した場合は 添字が0扱いで出力されているだけなんじゃないか? という疑問が湧いたので試してみます。

# 連想配列を作成するが、添字=0を埋め込む
$ declare -A MAP
$ MAP['A']=11
$ MAP['0']=22
$ MAP['B']=33

$ echo $MAP
22

# 添字=0 の要素が無い場合は何も表示されない
$ declare -A MAP2
$ MAP2['A']=11
$ MAP2['2']=44
$ MAP2['B']=33

$ echo $MAP2

どうやら正解。

-iオプション

整数として変数を定義。
= の右辺を数式として認識し評価結果を代入します。

# 整数型の変数を定義
$ declare -i N

# 値を代入(加算)
$ N=123+876
$ echo "$N"
999

# 自分自身に別の値を加算してみる
$ N=N+500

$ echo "$N"
1499

# 値を代入(減算)
$ N=100-1
$ echo "$N"
99

# 自分自身に別の値を減算してみる
$ N=N-600
$ echo "$N"
899

# 少数計算はどうか?
$ N=99.5-9.5
bash: 99.5-9.5: 構文エラー: 無効な計算演算子です (エラーのあるトークンは ".5-9.5")
# ダブルクォーテーションて囲っては?
$ N="99.5-9.5"
bash: 99.5-9.5: 構文エラー: 無効な計算演算子です (エラーのあるトークンは ".5-9.5")

整数しか取扱できません。

意地が悪い性分ですので、文字列を足し算してみたらどうか試してみたくなりました。

# こいつはいける
$ N="123"+456
$ echo "$N"
579

# こいつもいける!
$ N="100"+"1"
$ echo "$N"
101

# こいつもいける!!
$ N="100"+"01"
$ echo "$N"
101

# 先頭0の数値代入がおかしい!?
$ N="0100"
$ echo "$N"
64

# どうやら8進数扱いとなっている
$ N="0101"
$ echo "$N"
65

# 超えられない壁もある
$ N="09"
bash: 09: 基底の値が大きすぎます (エラーのあるトークンは "09")

# これは、8進数側を10進数に変換して演算している
$ N="0100"+456
$ echo "$N"
520

# 両方8進数でもやっぱり10進数に変換している
$ N="0100"+"010"
$ echo "$N"
72

8進数演算可能なことがわかったのは大きい。特に会社メンバで Clash of code で戦うときにかなりの強みになります(笑)

とはいえ、実は別の記述で代用可能です。

$ N=$(("123"+456))
$ echo "$N"
579

$ N=$(("0100"+"010"))
$ echo "$N"
72

文字列が格納されない安心感は生まれるが、数値演算は $((...)) で囲ってやったほうが読みやすいと思います。

ひとこと

今日はここまで。
時間がないのでまた次回。

2023-03-27Bash,CentOS,Cygwin,Linux,Ubuntu