シェルの変数に慣れる:ステップ・バイ・ステップ・シェルスクリプト(5)
ほとんどすべてのプログラム言語やスクリプト言語には「変数」というものが存在します。これはプログラマーが指定したある名前の「変数」に、プログラムの実行状況によって、文字列や数値などさまざまなデータを記憶しておくものです。
ユーザー定義変数の使い方
変数は、シェルの世界、とくに英語のドキュメントでは「パラメータ」と呼ばれていますが、日本語でパラメータというとどうも違う意味にとられがちなので、ここでは「変数」と呼ことにしたいと思います。ではシェルスクリプトにおける「変数」の取扱と特徴について見ていくことにしましょう。
シェルやシェルスクリプトで変数を定義する場合は、
name=value
のように記述します。とくに前もって変数を宣言したりする必要はありません(宣言することもできますが)。‘=’の両側にスペースをあけたりしてはいけません。C言語などの変数代入などの際にスペースをあける癖のある方は気をつけてください。
格納された値を参照する場合は、変数の先頭に‘$’をつけます。ためしにechoを使って標準出力に変数を表示してみます。
echo $name
変数の境界を明示するために‘{’と‘}’を使用する場合もあります。この方法のほうが、‘$’のあとがどこまでが変数なのか判別しやすいので、変数を参照する場合にはかならず‘{’と‘}’を使用する、としておいたほうがよいでしょう。
echo ${name}
実際の実行段階において、値への「変換」はシェルによって行なわれます。つまり上記の例においてはシェルにより
echo value
というコマンドが実行された場合と全く同じになります。言いかえると、変数は値に置き換えられてからコマンド全体が実行されるのです。つまり、echoは変数nameを受け取っているわけではなく、シェルによって変換された値「value」を受け取って処理しているのです。echoは引数が変数であったか、ただの文字列であったか知る術はありません。
変数の有効範囲は?
一度定義された変数はそのシェルの中だけで有効です。新たに起動されたシェルスクリプトの中で定義された変数は元のシェル(親シェル)は全く影響しませんし、逆に親シェルからは参照することができません。また、親シェルで定義した変数はそのままでは子供シェルでは参照できません。子においても参照したい場合は以下のようにexportを使います。
export name
exportコマンドは、変数nameをいわゆる「グローバル変数」化します。こうすることにより子供シェルもこの変数を参照することができます。面白いことに一度exportを実行すると、孫シェル、ひ孫シェル……とずっと有効となります。シェルにかぎらずこのシェルから起動されたすべてのコマンドがexportされた変数を参照することができます。しかしこれらのexportされた変数は子供シェルに「コピー」されて渡されるため、引き継がれた変数を子供シェルで変更したとしても親シェル内での変数の値は元のままです。
次回に説明する「関数」の内部においては、exportされているかどうかに関わらず元のシェルの変数を参照することができます(ただし、引数を参照する変数$1、$2、$3……は、関数内では元のシェルの値は参照できません。関数自体に渡された引数の値に置き換えられます)。そして関数内で変数が変更された場合は、呼出元のシェル内の変数の値も変わってしまいます。これは次回に説明いたしますが、シェルにおける関数はシェル内部で実行されているためです。
文字変数と数値変数
多くのプログラム言語においては文字変数と数値変数の使い分けがされていますが、シェルにおいてはこれらの使い分けはありません。すべての変数はただの文字列に置き換えられます。ちなみに変数を使って演算をしたい場合は以下のように通常exprを使います。
num=1 num=`expr $num + 1`
演算子と数字の間はかならずスペースをあけなくてはいけません。exprは内部コマンドではなく、通常のコマンドです。与えられた引数を数学演算とみなして計算を行ない、実行結果を「標準出力」へ出力するコマンドなのです。上記はその結果を再度変数numへ代入しているのです。この方法はボーンシェル(sh)と互換性があるため広く使われていますが、もし互換性を気にしないのであれば、bashにおいては上記以外に演算機能がサポートされていて、以下のようにも記述できます。
num=1 num=$[$num+1]
または $[$num+1]のかわりに
num=$(($num+1))
でもかまいません。“$[...]”または“$((...))”の中が数値演算して扱れます。この場合は‘+’の両側にスペースを入れても入れなくてもどちらでもかまいません。繰り返しますが、これはbashのみの機能で、オリジナルのボーンシェル(sh)にはない機能です。
また、bashには変数同士を演算に用いることができる内部コマンド「let」が用意されています。
num=3 mult=4 let result=num*mult $ echo $result 12
letに渡された演算式の中では、‘$’をつけなくても値が取り出され計算されます。この場合、もし数値以外の値をもつ変数がletに渡された場合は、「0」として計算されます。
配列変数
bash version2.0以降には配列変数がサポートされています。値のセットは以下のように
a[0]=a a[1]=b a[2]=c ……続く……
1つ1つ代入することができます。“[ ]”の中は、配列は0からはじまる整数をセットします。一度にセットしたい場合は、
a=(a b c d)
のように行ないます。これを応用すると、
files=(`ls`)
のようにすれば、lsコマンドの結果(つまりカレントディレクトリに存在するファイル名)を配列変数filesに格納することができます。
値の参照は
echo ${a[0]} echo ${files[3]}
のように行ないます。この場合はかならず“{ }”を使用しなくてはいけません。また、配列変数に格納されている値をすべてみたい場合は‘@’を使って、
echo ${a[@]}
のようにします。結果は
a b c d
のように配列のすべての値がスペースで区切られて表示されます。
bash2.0はまだ広く定着しているわけではなくて、多くのlinuxディストリビューションにおいてはまだbash 1.14が標準なので、この機能を使う場合は注意が必要です。bashのバージョンを調べるには以下のようにします。
bash -version
また、ディストリビューションによっては/bin/bash2という形でインストールされている場合もあります。シェルスクリプトの実行時のみ、そちらを使う方法もあります。お手持ちの環境を確認してみてください。
引数を扱う変数
前章で既に「引数」の扱いのところで変数が出てきましたが、シェルでは実行時の引数の値は変数$1、$2、$3……に格納されます。10個目以降の場合は“${10}”のように、中括弧で数字を囲む必要があります。もちろん$1?$9も${1}?${9}と記述してかまいません。
次のようなシェルを作成し、引数の動き方を確認してみましょう。ファイル名を「argtest」として次の内容を記述します。
#!/bin/bash num=1 while [ "$1" != "" ] ; do echo "Argument $num is " $1 shift 1 num=`expr $num + 1` done
さっそく実行権をつけて実行してみましょう。テストのために次のようにaからkまで引数を渡して実行してみます。
$ chmod +x argtest $ ./argtest a b c d e f g h i j k Argument 1 is a Argument 2 is b Argument 3 is c Argument 4 is d Argument 5 is e Argument 6 is f Argument 7 is g Argument 8 is h Argument 9 is i Argument 10 is j Argument 11 is k
命令shiftは、一度実行される度に、引数がセットされている変数$1、$2、$3……の格納場所を1つずつ前にもってくる働きをします。つまり、$2に入っていた値を$1へ、$3にはいっていた値を$2へ……のように全体を移動させます。$1に入っていた値は捨てられてしまいます。伝統的にshではこのようにshiftを使って引数処理を行ないます。$1、$2、$3……は上記で紹介した配列変数とは別のものであるために、配列として扱うことができません。どうしても必要な場合は、以下のように「eval」を使って行ないます。若干複雑になってしまいますが、使用例を紹介します。
#!/bin/bash num=1 while [ $num -le $# ] ; do eval echo "Argument $num is " \$\{$num\} num=`expr $num + 1` done
命令evalは引数を一度「評価」し、それらを改めてコマンドとして実行します。「評価」とは変数を展開することです。つまり、上記の例では環境変数$numが値に置き換えられた後にevalによって実行されるわけです。‘\’の後の‘$’は特別な意味をもたない文字と認識されますので、evalの引数は結局
echo "Argument 1 is " ${1}
となります。数値の部分がwhileのループ注に順次変わって実行されていくわけです。
evalの使い方は少し分かりにくいかもしれません。無理に使うことはないですが、このコマンドの存在は覚えておくと便利なので、頭のすみにとどめておいて下さい。
コロンの使い方
変数に値をセットせずに、いきなり参照するとどうなるのでしょう?。実はただ単に空白(Null)として扱われます。シェルスクリプトでは値がセットされているかどうか判定する場面がよく出てきますので、そのような場合には以下のようなやり方ができます。
echo ${name:-value}
もし変数nameが空白(セットされていない場合も含む)であれば、「value」がその値となります。以下のコマンド郡と同じような意味となります。
if [ "${name}" != "" ] ; then echo ${name} else echo value fi
上記${name:-value}と同じように、‘:’の後ろの記述の仕方によって、さまざまな表現ができます。覚えておくと便利でしょう。
${name:+value}
上記と逆で、nameが空白でなければ「value」がその値となります。
${name:=value}
${name:-value}と同じですが、変数nameに値として「value」をセットします。
${name:?value}
もしnameが空白であればvalueを「標準エラー出力」に出力し、シェルスクリプトの実行を終了します。シェルがインタラクテイブモードの場合は終了しません。特定の変数がシェルスクリプトの実行の必須条件になっている場合に使われます。
${name:start} ${name:start:length}
変数nameの「start」バイト目から「length」文字出力します。lengthを省略すると「start」から最後まで表示します。この機能はbash version2.0以降でサポートされています。
その他の特殊変数
上記以外にも特殊な変数が用意されています。紹介しておきましょう。
$#
上記の例でも使っていますが、引数を受ける変数$1,$2,$3...に実際に値が入っている数、つまりシェルスクリプト実行時に与えられた引数の数を表します。
$?
直前に実行されたコマンドのステータス(終了フラグ)を表します。これを使って実行されたコマンドが正しく終了したかどうか判定します。
$$
実行中のシェルのプロセスIDを表します。実行されているOSの中でユニークとなるので、テンポラリファイルを作成する場合などに使われます。
$0
実行されたシェルスクリプトの実行ファイル名を表します。
$*
引数変数$1、$2、$3……をスペースで区切ってすべて表示します。シェルの予約変数IFSに値をセットしておくと、区切り文字を変更することができます。この場合はダブルクオーテーション(")で囲む必要があります。例えば以下のように使用できます。
$ IFS=: $ echo "$*" a:b:c:d
$@
上記「$*」とほとんど同じですが、予約変数「IFS」の影響は受けません。つねに区切り文字は空白です。
上記「@」と「*」は上記で紹介したbash version2.0 の機能である配列変数にも応用できます。
$ IFS=: $ array=(a b c d) $ echo "${array[*]}" a:b:c:d $ echo "${array[@]} a b c d
シェルの変数の話はいかがでしたでしょうか? シェルの変数はなかなか使いにくいところもありますが、シェルスクリプトを使うのにはなくてはならないものです。実際に使いながら徐々に慣れていってください。
Copyright © ITmedia, Inc. All Rights Reserved.