変数の扱い方

ここまでに変数を使ってきていますが、変数はどんな使い方ができるのかまとめてみましょう。

まずは今まで使ってきた方法から

変数名=値

で、変数に値を代入します。
変数に格納された値を取り出すのは変数名の頭に「$」を付与します。
その際、変数名は「{}」で括った方が良いんでしたよね。

${変数名}

そして、コマンド等の出力結果も変数に格納することができましたね。
コマンドの出力結果を格納する時はこうでした。

変数名=`コマンド`

「`(バッククォート)」で実行したいコマンドを括ってやる事で、
右辺のコマンドの実行結果を左辺の変数に格納できます。

コマンドの出力結果の格納された変数を参照するには「"(ダブルクォーテーション)」で括ってやる必要がありました。
これは、格納してあるコマンド出力結果を「文字列」として扱うからでしたね。

"${変数名}"

他にも変数内に文字列が格納されている時は「"」で括ってやる事で正しく参照できました。
では、格納されているのが数字の場合はどうだったでしょうか?
確か、こんな感じで使ってましたね。

変数名=`expr ${変数名} + 1 `

右辺の「expr」コマンドで変数に1を足して、再度同じ変数に格納するこんな処理を「インクリメント」と言うんでした。
ここで右辺の変数に対し1をプラスして左辺の変数に「普通に」格納してますが、この処理は厳密に言うと大変な問題を抱えています。

実は、bashでは変数に値を代入する際、普通に代入すると数字も「文字列」として代入してしまいます。

「expr」コマンドは、それが「数字」を扱っているか「文字列」を扱っているのかを式に応じて「自動」で判断して演算を行ってくれています。
その為、上記の様な「変数(文字列) + 1」という式を変数に数字が入っていれば数値演算と判断して自動で演算を実行してくれているだけなのです。
ですので、「expr」コマンドによって、上記の式は成立していますが、厳密に言うと、「文字列 + 数字」という式に他なりません。

「成立してるんだからいいんじゃないか?」なんて声も聞こえてきそうですが、
よーく考えてみると、気持ち悪いとは思いませんか?
というのも、他のプログラミング言語では、変数を宣言する際に型指定する物が多いのです。
bashの変数は型指定をしないで宣言できる為、こういった気持ち悪い事ができてしまうのです。

こういう気持ち悪さを解消する為(?)に、bashでも変数の型を宣言する事ができます。
型指定で宣言する為に、「declare」と「typeset」という組み込みコマンドが用意されています。
両方共、使用できる機能は同じなのですが、最近では「typeset」は使われなくなってきている様ですので、ここでは「declare」を使って説明します。(引数は両コマンド共に一緒です)

「declare」コマンドでは以下の属性を指定できます。

-a NAME    NAMEを配列として扱う
-i NAME    NAMEを整数として扱う
-r NAME    NAMEを読み取り専用の変数として扱う
-x NAME    NAMEをエクスポートし、環境変数として扱う

現在使われている変数を参照する為に、以下のオプションもあります。

-p NAME    NAMEに付与されている属性と値を表示する。
-f         関数を表示する
-F         関数名を表示する

また、「-a」と「-r」の属性以外は、適時変更することができます。
変数に対し、属性をつける場合は上記の様に使用し、属性を解除する場合は
以下のオプションを使用します。

+i NAME    NAMEの整数属性を解除する
+x NAME    NAMEの環境変数属性を解除する

先述の通り、bashでは標準で変数への代入を「文字列」として扱う為、「declare」コマンドには文字列の属性は用意されていません。「-p」オプションで確認した際、なにもオプションが付いていなければ、文字列と考えましょう。

では、上記の属性を検証してみます。

・文字列と整数
こんなスクリプトで検証してみます。

#!/bin/bash

## 文字列変数を評価
echo "文字列変数を評価"

V_TEST01=""
echo "空文字列を定義"
declare -p V_TEST01

V_TEST01="test01"
echo "文字列を定義"
declare -p V_TEST01

V_TEST01="111"
echo "数字(文字列)を定義"
declare -p V_TEST01

echo "変数内が数字(文字列)であれば整数に変更できる"
declare -i V_TEST01
declare -p V_TEST01

echo "変数が整数属性になると、文字列を定義できない(やると0が定義される)"
V_TEST01="test01"
declare -p V_TEST01
echo ""

echo "文字列が定義してあっても整数属性にできる"
V_TEST02="test02"
declare -p V_TEST02
declare -i V_TEST02
declare -p V_TEST02

echo "但し、次の代入からは文字列を受け付けなくなる(0が定義される)"
V_TEST02="test03"
declare -p V_TEST02


echo "整数属性を解除してやれば、再度文字列が定義できる"
declare +i V_TEST02
declare -p V_TEST02
V_TEST02="test03"
declare -p V_TEST02

exit 0

実行します。

# sh variable01.sh 
文字列変数を評価
空文字列を定義
declare -- V_TEST01=""
文字列を定義
declare -- V_TEST01="test01"
数字(文字列)を定義
declare -- V_TEST01="111"
変数内が数字(文字列)であれば整数に変更できる
declare -i V_TEST01="111"
変数が整数属性になると、文字列を定義できない(やると0が定義される)
declare -i V_TEST01="0"

文字列が定義してあっても整数属性にできる
declare -- V_TEST02="test02"
declare -i V_TEST02="test02"
但し、次の代入からは文字列を受け付けなくなる(0が定義される)
declare -i V_TEST02="0"
整数属性を解除してやれば、再度文字列が定義できる
declare -- V_TEST02="0"
declare -- V_TEST02="test03"
# 

文字列だと「declare」コマンドで状態を表示した時には何も属性が表示されません。
標準の状態だと文字列として扱われる事が判りましたでしょうか。

・配列

こんなスクリプトで検証してみます。

#!/bin/bash

## 配列変数を評価
echo "配列変数を評価"

V_TEST01=""
echo "空文字列を定義"
declare -p V_TEST01

V_TEST01="test00"
echo "文字列を定義"
declare -p V_TEST01

V_TEST01[1]="test01"
echo "2番目の要素を追加する。(この時点で配列属性に変化する)"
declare -p V_TEST01

echo "配列にも整数属性を定義できる"
declare -i V_TEST01
declare -p V_TEST01

echo "整数属性の変数になると、文字列の要素は受け付けない(代わりに0が代入される)"
V_TEST01[2]="test02"
declare -p V_TEST01

echo "もちろん、既存の要素も文字列の要素は受け付けない(代わりに0が代入される)"
V_TEST01[0]="test03"
declare -p V_TEST01

echo "当然の事ながら、整数の要素は受け付ける"
V_TEST01[1]="111"
declare -p V_TEST01

echo "整数属性を解除すれば文字列要素も定義できる"
declare +i V_TEST01
V_TEST01[0]="test04"
declare -p V_TEST01

echo "最後に、配列属性を解除しようとするとエラーとなる"
declare +a V_TEST01
declare -p V_TEST01

exit 0

実行します。

# sh variable02.sh 
配列変数を評価
空文字列を定義
declare -- V_TEST01=""
文字列を定義
declare -- V_TEST01="test00"
2番目の要素を追加する。(この時点で配列属性に変化する)
declare -a V_TEST01='([0]="test00" [1]="test01")'
配列にも整数属性を定義できる
declare -ai V_TEST01='([0]="test00" [1]="test01")'
整数属性の変数になると、文字列の要素は受け付けない(代わりに0が代入される)
declare -ai V_TEST01='([0]="test00" [1]="test01" [2]="0")'
もちろん、既存の要素も文字列の要素は受け付けない(代わりに0が代入される)
declare -ai V_TEST01='([0]="0" [1]="test01" [2]="0")'
当然の事ながら、整数の要素は受け付ける
declare -ai V_TEST01='([0]="0" [1]="111" [2]="0")'
整数属性を解除すれば文字列要素も定義できる
declare -a V_TEST01='([0]="test04" [1]="111" [2]="0")'
最後に、配列属性を解除しようとするとエラーとなる
variable02.sh: line 40: declare: V_TEST01: cannot destroy array variables in this way
declare -a V_TEST01='([0]="test04" [1]="111" [2]="0")'
#

配列変数も基本的には普通の変数と同じです。違うのは複数要素を扱える事と、一度定義した配列属性は解除できない事です。

・関数表示
こんなスクリプトで検証してみます。

#!/bin/bash

## 関数属性変数を評価
echo "関数属性変数を評価"

## 関数を定義する
function F_TEST10()
{
    echo "test10"
    return 0
}

echo "関数名を表示"
declare -F
echo ""
echo "関数を表示"
declare -f

exit 0

実行します。

# sh variable03.sh 
関数属性変数を評価
関数名を表示
declare -f F_TEST10

関数を表示
F_TEST10 () 
{ 
    echo "test10";
    return 0
}
# 

そのまんまですが、定義されている関数と関数名を表示します。
ちょっと違うのは、関数を表示した時に、スクリプトで定義してある構文とは変わる所です。
ですが、意味合いは一緒ですので問題はありません。

・読み取り専用
こんなスクリプトで検証してみます。

#!/bin/bash

## 読み取り専用変数を評価
echo "読み取り専用変数を評価"

## 普通の文字列変数を定義
V_TEST01=AAA

## 普通の整数変数を定義
declare -i V_TEST02=111

## 普通の配列を定義
A_TEST03=(aaa bbb)

## 全ての変数を評価する
echo "全ての変数を評価する"

declare -p V_TEST01
declare -p V_TEST02
declare -p A_TEST03

echo "全ての変数を読み取り専用にする"
declare -r V_TEST01
declare -r V_TEST02
declare -r A_TEST03

declare -p V_TEST01
declare -p V_TEST02
declare -p A_TEST03

echo "全ての変数の読み取り専用属性の解除を試みる"
declare +r V_TEST01
declare +r V_TEST02
declare +r A_TEST03

declare -p V_TEST01
declare -p V_TEST02
declare -p A_TEST03

echo "もちろん変数の破棄もできない"
unset V_TEST01
unset V_TEST02
unset A_TEST03

echo ""

echo "読み取り専用属性の変数に代入を行うとスクリプトが終了する。"
echo "スクリプト終了後に終了コードを表示してみよう"
V_TEST01="BBB"

echo "上記の代入で異常終了してるので、これは表示されない"
exit 0
# 

実行してみます。

# sh variable04.sh 
読み取り専用変数を評価
全ての変数を評価する
declare -- V_TEST01="AAA"
declare -i V_TEST02="111"
declare -a A_TEST03='([0]="aaa" [1]="bbb")'
全ての変数を読み取り専用にする
declare -r V_TEST01="AAA"
declare -ir V_TEST02="111"
declare -ar A_TEST03='([0]="aaa" [1]="bbb")'
全ての変数の読み取り専用属性の解除を試みる
variable04.sh: line 32: declare: V_TEST01: readonly variable
variable04.sh: line 33: declare: V_TEST02: readonly variable
variable04.sh: line 34: declare: A_TEST03: readonly variable
declare -r V_TEST01="AAA"
declare -ir V_TEST02="111"
declare -ar A_TEST03='([0]="aaa" [1]="bbb")'
もちろん変数の破棄もできない
variable04.sh: line 41: unset: V_TEST01: cannot unset: readonly variable
variable04.sh: line 42: unset: V_TEST02: cannot unset: readonly variable
variable04.sh: line 43: unset: A_TEST03: cannot unset: readonly variable

読み取り専用属性の変数に代入を行うとスクリプトが終了する。
スクリプト終了後に終了コードを表示してみよう
variable04.sh: line 49: V_TEST01: readonly variable
# echo $?
1
# 

上記のスクリプトを作成して試してみて「おかしい」と思ったのが、読み取り専用属性の解除(+r)です。
これは昔は出来た様な気がします。ログが残ってないのではっきりとは断言できませんが・・・。
ですが、過去の記憶に囚われていてもいけません。事実は事実なので、「+r」についてはできないと言う事でここでは上記の様な検証スクリプトになりました。

変数の型指定の方法は掴めましたか?
この様な変数の型指定は、うまく使えばバグを軽減する事ができます。
また、あまりガチガチに属性を決めてしまうとバグの元になったりもします。
うまく扱ってください。