4. シェルプログラミング(1) |
シェル変数やエイリアスを適切に設定することで さまざまな環境設定が可能であることを 3. で学んだ. しかしながら, それでもなお十分でないと感じるユーザーも少なくないであろう. ここではそういったカスタマイズの延長でもある シェルプログラミングについて見ていく. | ||
<-- 「3. 環境のカスタマイズ」へ戻る | ↑ 目次へ戻る | 「5.シェルプログラミング(2)」へ進む --> |
4.1 シェルスクリプトと関数4.2 シェル変数
4.3 文字列演算子
4.4 コマンド置換
4.1 シェルスクリプトと関数シェルスクリプトとはシェルコマンドが収められたファイル, つまりシェルプログラムを意味する. 前述した .bash_profile 等はシェルスクリプトの一つである. これを実行するには次の 2 つの方法がある:
4.2 シェル変数
- source コマンドで実行する.
- シェルスクリプトに実行権を与えて直接実行する.
source コマンドで実行する場合,プロンプト上で
と入力する. これはスクリプトファイルの内容を手で一つずつ入力するのと何ら変わらない.
$ source スクリプトファイル 一方,スクリプトに実行権を与える ( chmod +x ファイル名 ) と,それはまさに一つのコマンドとして働く. このとき,このスクリプトは(現在のシェルの子プロセスである) サブシェル上で動く ことに注意しなくてはならない. すなわち, 環境変数でないシェル変数 は現在のシェルからは 引き継がれない.
(例)いま,カレントディレクトリに次の 1 行だけからなる aaa というシェルスクリプトを作成したとする:
echo $bbb ここで,シェル変数 bbb に適当な文字列を設定する:
そして,source コマンドで実行すると
$ bbb="This is a shell-script" と表示されるが,aaa に実行権を与えて実行させても
$ source aaa This is a shell-script ← 出力空の行(NULL の意味)が出力される. これは bbb が環境変数でないからである.
$ chmod +x aaa $ ./aaa ← 出力
環境変数に昇格させると,となる.
$ export bbb $ ./aaa This is a shell-script ← 出力
- 関数
関数はスクリプトと異なり, 定義されるとその名前と内容はメモリに格納される. つまり,スクリプトのように PATH で指定されたディレクトリにその定義ファイルを置いておく必要はない. また,スクリプトの中のサブスクリプトとして利用されることもできる.
関数の定義は
または,
function 関数名 { シェルコマンド群 }の書式で行われる.
function 関数名 () { シェルコマンド群 }これらの実行の優先順序にも注意が必要である. シェルに対して入力されたコマンドは
- エイリアス
- if や for などのキーワード
- 関数
- cd や type などの組込みコマンド
- スクリプトや実行可能ファイル
の順に優先順位が付けられている. したがって,同じ名前の関数とシェルスクリプトがある場合は, 関数の方が優先される.4.3 文字列演算子
- 位置パラメータ
位置パラメータとは, スクリプトが呼び出された際の コマンドライン引数を保持 するための特別な組込み変数である. これを下表に示す.
位置パラメータ 変数名 説明 0 スクリプトの名前 1, 2, 3, ... 1番目,2番目,3番目,... のコマンドライン引数 * $1, $2, $3, ... すべて @ $* とほとんど同じ # 位置パラメータの数($0 を数えない) これらの働きを見るため,次のようなスクリプトを考えてみる:
echo "-----------------------------" echo "このスクリプトの名前は $0 です" echo "$0 には $# 個の引数が渡されています" echo "それらは $* です" echo "1 番目= $1" echo "2 番目= $2" echo "3 番目= $3" echo "4 番目= $4" echo "-----------------------------"これを aaa という名前でカレントディレクトリに置き, 実行権を与えて適当な引数を付けて実行してみる:
$ ./aaa a b c ----------------------------- のスクリプトの名前は ./aaa です ./aaa には 3 個の引数が渡されています それらは a b c です 1 番目= a 2 番目= b 3 番目= c 4 番目= -----------------------------この場合,引数は 3 個しかないので 4 番目の $4 は空文字列になっている.
上の表で $@ を $* とほとんど同じであると記した. それらの違いは非常に微妙であり,上の例を用いると
"$*" が "a b c" に展開されるのに対し,という点が唯一の違いである. (ここでのダブルクォーテーションは大切で, これが無ければ 2 つに違いはない.)
"$@" は "a" "b" "c" と別々の文字列に展開される例えば, 引数リストをそっくりそのまま別のスクリプトや関数に渡す場合, "$*" は 1 つの文字列として解釈され, "$@" はそれぞれ別々の n 個の文字列として解釈されるという違いがある.
- 関数における位置パラメータ
関数でも位置パラメータの使い方はスクリプトと同じである.
したがって,上記のスクリプトの関数版を ~/.bash_profile あるいは ~/.bashrc で定義しておくと,
function aaa { echo "-----------------------------" echo "この関数の名前は $0 です" echo "$0 には $# 個の引数が渡されています" echo "それらは $* です" echo "1 番目= $1" echo "2 番目= $2" echo "3 番目= $3" echo "4 番目= $4" echo "-----------------------------" }という具合いに $0 を除いて同じ動きをする. ではこの関数を別のスクリプトに埋め込んでみる.
$ aaa ----------------------------- この関数の名前は bash です bash には 3 個の引数が渡されています それらは a b c です 1 番目= a 2 番目= b 3 番目= c 4 番目= -----------------------------
function aaa { echo "-----------------------------" echo "この関数の名前は $0 です" echo "$0 には $# 個の引数が渡されています" echo "それらは $* です" echo "1 番目= $1" echo "2 番目= $2" echo "3 番目= $3" echo "4 番目= $4" echo "-----------------------------" } echo ":::::::::::::::::::::::::::::::::::::::" echo "関数 aaa に 2, 3 番目の引数を渡します." aaa $2 $3 echo ":::::::::::::::::::::::::::::::::::::::"このスクリプトを bbb として,実行すると
という具合いに関数内の $1 , $2 には ./bbb が渡した $2, $3 がそれぞれ対応し, $0 にはスクリプト名である ./bbb が対応する結果となった. つまり, $0 はスクリプト内でグローバル変数 であるが, それ以外は関数内でのローカルな変数 として取り扱われている.
$ ./bbb a b c ::::::::::::::::::::::::::::::::::::::: 関数 aaa に 2, 3 番目の引数を渡します ----------------------------- この関数の名前は ./bbb です ./bbb には 2 個の引数が渡されています. それらは b c です 1 番目= b 2 番目= c 3 番目= 4 番目= ----------------------------- :::::::::::::::::::::::::::::::::::::::
- 関数のローカル変数
位置パラメータであるシェル変数は $0 を除いて関数ではローカルな変数であることを上で述べた. では位置パラメータではないシェル変数の場合はどうだろうか. この場合は基本的に グローバル変数となる. そのため関数の中だけで有効なローカル変数を使いたい場合は,
local 変数名と関数中で宣言する. 例えば, local var としておけば,関数の外で var が定義されていても,それとは独立に var を設定・利用できる.
- ブレース( { } )構文
今まで,変数へのアクセスは
$変数名で行うと述べてきた. しかしながら,これは常に正しいとは言えない. 例えば,シェルスクリプトに対する 10 番目の引数を参照する場合,$10と記述するであろうが, これは $1 と "0" として解釈され, 1番目の引数の内容に 0 をくっつけた文字列に展開されてしまう.
より正確な変数へのアクセスは${変数名}とブレース( { } )を付けて行う. なお,変数名の後に,アルファベット,数字, アンダースコア以外の文字が続く場合,ブレースは必要ない.文字列演算子を使用すると, 変数の値を操作する便利な方法をいろいろ利用することができるため, 変数操作のために特別に perl スクリプトを書いたり UNIX ユーティリティに助けを求める必要はない.
特に文字列演算子では次のことが可能である:
- 変数の存在を確定する
- 変数にデフォルトの値を設定する
- 変数に値が設定されなかったときのエラーをキャッチする
- 変数の値においてパターンと一致する部分を削除する
- 置換演算子
まずは,変数の存在を評価し, 特定の条件のもとでデフォルトの値を置換する置換演算子を 下表にまとめておく.
演算子 置換内容 ${varname:-word} varname が存在し NULL ではない場合, その値を返す.それ以外は,word を返す. ${varname:=word} varname が存在し NULL ではない場合, その値を返す.それ以外は,varnameに wordを設定して返す. ただし,位置パラメータや特殊なパラメータを除く. ${varname:?message} varname が存在し NULL ではない場合, その値を返す.それ以外は varnameのあとに message を出力し, 現在のコマンドあるいはスクリプトを中止する. message を省略すると初期メッセージ parameter null of not setが生成される. ${varname:+word} varname が存在し NULL ではない場合, word を返す.それ以外は NULL を返す. これらにはそれぞれ次のような用途がある.
(例)スクリプト aaa を以下の内容で作成し,実行権を与えておく:
- 変数が未定義の場合にデフォルトの値を返す
${count:-0} count が未定義の場合,0 と評価される.
- 変数が未定義の場合にデフォルトの値を設定する
${count:=0} count が未定義の場合,それに 0 を設定する.
- 変数が未定義の場合に結果として生成されるエラーをキャッチする
${count:?"undefined!"} count が未定義の場合, "count : undefinded!" を出力して終了する
- 変数の存在を評価する
${count:+1} count が未定義の場合,1 (真を意味する) を返す
これを実行すると,
# # 第1引数を答える # echo "第1引数 = ${1:?"引数がありません"}"となる.
$ ./aaa ./aaa: 1: 引数がありません $ ./aaa this 第1引数 = this
- パターンとパターン照合
次にパターン照合演算子について見てみる.
演算子 意味 ${variable#pattern} variable の値の最初の部分と pattern が一致した場合, もっとも短く一致する部分を削除して残りの部分を返す ${variable##pattern} variable の値の最初の部分と pattern が一致した場合, もっとも長く一致する部分を削除して残りの部分を返す ${variable%pattern} variable の値の終りの部分と pattern が一致した場合, もっとも短く一致する部分を削除して残りの部分を返す ${variable%%pattern} variable の値の終りの部分と pattern が一致した場合, もっとも長く一致する部分を削除して残りの部分を返す (例)次のスクリプトを考える:
aaa=/usr/local/bin/mule echo "(1) $aaa" echo "(2) ${aaa#/*/}" echo "(3) ${aaa##/*/}" echo "(4) ${aaa%/*/}" echo "(5) ${aaa%%/*/}"これを実行すると
となる. (2) ,(3) は先頭から /*/ というパターンにマッチする. そこで,(2) は最も短い /usr/ に, (3) は最も長い /usr/local/bin/ にそれぞれマッチするためそれらが削除されて上の結果になる. また,(4),(5) は同じパターンマッチを後ろから試みているが, この aaa の値は / で終っていないのでマッチしない. これから予想が付くかと思うが,
(1) /usr/local/bin/mule (2) local/bin/mule (3) mule (4) /usr/local/bin/mule (5) /usr/local/bin/muleという一行のみのスクリプトを作成し, これを gettail と名付けると,
echo ${1##*/}と絶対パスや相対パスで書かれたファイル名からパスのみ を取り除くことができる. これはおそらく perl で書くよりも楽であろう.
$ gettail /usr/local/share/texmf/tex/platex/base/jarticle.cls jarticle.cls $ gettail ./memo.txt memo.txt
- 長さを返す演算子
もう 1 つだけ変数に対する演算子が残っている. それは
${#variable}である. これは変数 variable の長さを返す.これはコマンドの標準出力を変数の値として利用する機能を意味している.
Bourne シェルや C シェルではバッククォートでコマンドを囲むことにより, このコマンド置換を実現していた. 例えば,というスクリプトを実行すると
file=`ls` echo "======= $PWD =======" echo $fileといった具合に普通に ls コマンドを実行したのと変わらない.
======= /home/hogehoge ====== aaa.c bbb.cc ccc.o ddd.h eee fff.java
ただし,バッククォートで囲む方法では,入れ子にすることができない. そのため,bash では$(コマンド)という書式をとっている.(互換性のため,バッククォートも使える.)
つまり,上の例はとして書ける. また,入れ子にするときは,
file=$(ls) echo "======= $PWD =======" echo $filefile=$(ls $(pwd))のように書く.
echo $file