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

2019-07-10Bash, CentOS, Cygwin, Linux, Ubuntu

シェルスクリプトサンプルコードでよく見かけるtypesetやdeclareってなに? の続きです。

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

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

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

検証環境

$ bash --version
GNU bash, バージョン 5.0.2(1)-release (x86_64-apple-darwin18.2.0)

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[@]}"
33 22 11

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

# -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[@]}"
33 22 11

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

# -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

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

ひとこと

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

2019-07-10Bash, CentOS, Cygwin, Linux, Ubuntu