シェル・プログラミング:全ページ版
* 以下の各項が1ページ(約3k程)となっているスライド版もあります。内容やリンクに変化はありません。
目次
- 1. メタ文字とその処理
- 2. 変数:定義と代入
- 3. メタ文字からのエスケープ
- 4. スクリプト内のコメント
- 5. 演算
- 6. if 文
- 7. case 文
- 8. for 文
- 9. while 文
- 10. until 文
- 11. ループから抜ける:
break - 12. ループ中の作業の一部を飛ばす:
continue - 13. 関数
1. メタ文字とその処理
UNIX入門「4. 正規表現」において,コマンド echo * が,現在作業中のディレクトリのファイル一覧を表示することをみました。
% cd ~/My_Programs % echo * hello.c hello.out hello.pl hello.sh sample.sh %
これに対し,次はどうなるでしょうか?
% echo Hello!
結果は...
% echo Hello! Hello! %
何故なのでしょうか?UNIX入門「4. 正規表現」では,正規表現を表わす *, [ ], ? があり,今回の場合はありません。実は,シェルは,そのような特殊文字を最初に解釈するという特性を持ちます。そのような特殊文字を見つけると,それらの意味を解釈し,処理するのです。例えば, * を見つけると,カレント・ディレクトリを読み込み,文字検索を開始する訳です。実際,次を実行してみましょう。
% echo *Hello! echo: No match %
あくまでも,文字列検索をしていますね。標準入力の "*Hello!" すら,返しません。
このような特性,すなわち,先ず処理上特別な意味をもつ文字を解釈するという特性は,入出力におけるパイプ "|" やリダイレクト "<" や ">" にも適用されます。このような処理上特別な意味をもつ文字を「メタ文字」といいます。この特性を理解した上で,UNIX の操作,およびプログラミングをすることになります。
■メタ文字 (Meta Characters)
処理上,特別の意味をもつ文字。シェルでのメタ文字の例。
/ \ " ` ' * | ! ? ~ $ < > & [ ] #
注) [ と ] はペアのとき,シェルはそれらを特別な意味に解釈する。
] と [ ならば,文字検索とは解釈しない。
2. 変数:定義と代入
メタ文字は,変数名に使用可能ではないことは既に理解できたことでしょう。そこで,変数名に使用可能な文字,設定ルール,変数への値の代入を見てみましょう。
- 変数に使用可能な文字:
0-9, a-z, A-Z, _. 但し,頭文字に数字は使用できない。 - 変数
varに値valueを代入:var=value. 但し,=の前後にスペースを入れない。 - 変数
varの参照:$var################################################# # 例 1 # ファイル file1 をソートし, # それを file2 へ書き込む ################################################# #!/bin/sh aaa=file1 # 変数 aaa に file1 を代入。= の前後にスペースを入れない。 bbb=file2 sort $aaa > $bbb # 変数 aaa を $ を付し参照。sort はコマンド。> はリダイレクト。
- 変数にターミナルから値を代入
ケース1) コマンドラインからの代入
% sh script value1 value2 ... value9
value1は$1という変数に格納,value2は$2という変数に格納,以下9個まで格納。$*という変数に$1から$9すべてを格納。$#という変数に引数(格納した変数)の個数を格納。ちなみに,$0という変数にはスクリプト名が格納される。######### # 例 2 ######### #!/bin/sh echo $0 written by Taro. (C) 2002 Taro. echo $# words inputed. echo $* echo The first word is: $1
実行例) % sh script Hello World! script written by Taro. (C) 2002 Taro. 2 words inputed. Hello World! The first word is: Hello %
ケース2) 相手に尋ねる:
read var######### # 例 3 ######### #!/bin/sh echo -n "Enter your message: " # echo -n はコマンド read message # message という変数に格納 echo $message echo Goodbye.
実行例) % sh script Enter your message: How do you do? How do you do? Goodbye. %
- 変数にコマンドの値を代入:
var=`command`######### # 例 4 ######### #!/bin/sh num=`ls | wc -w` # ls と wc -w はコマンド。| はパイピング。 echo $num files in the current directory.
実行例) % sh script 6 files in the current directory. %
- スペース/タブで区切られた文字列を変数
$*($1~$9) に代入set character1 character2 ... character9
######### # 例 5 ######### #!/bin/sh set `echo *` echo $# files in the current directory. echo $*
実行例) % sh script 6 files in the current directory. example.sh hello.c hello.out hello.pl hello.sh outcome.txt %
- その他特殊な変数
$$ プロセス番号 $? 最後に実行したコマンドの終了状態。番号で表示。正常は0。 $@ これをダブル・クウォーテイションで囲まない場合,$* と同じ。 sh script 'Hello World!' としたとき,Hello World を 一つとしたい場合,これをダブル・クウォーテイションで囲む。 - 変数と文字の連結
例えば,変数
varに文字 "exp" が代入されていたとしましょう。このとき,次を実行するとどうなるでしょうか。#!/bin/sh var=exp echo $varport
実は,"
export" とは出力されません。この場合,次のようにすると良いです。#!/bin/sh var=exp echo ${var}portすると,変数
varを正しく解釈され,"export" と出力されます。
3. メタ文字からのエスケープ
シェルはメタ文字を先ず解釈し処理するわけですが (第1項参照),その解釈からエスケープする方法があります。
■メタ文字よりのエスケープ
'meta', "\meta"
########## # 例 6 ########## #!/bin/sh var=Test echo $var echo '$var' echo '\$var' echo "$var" echo "\$var"
実行結果) % sh script Test (変数 var に値が代入され,それを表示) $var (メタ文字 $ をエスケープ) \$var (メタ文字 \ も $ もエスケープ) Test (1行目と同じ処理) $var ($ をエスケープ) %
ダブル・クウォーテイション内では,$,\,`command` の3つをメタ文字と解釈します。すなわち,ダブル・クウォーテイション内ではそれら3つからのエスケープはできません。しかしながら,例6より,$ と \ を解釈し,バックスラッシュ (\) がダブル・クウォーテイション内でのエスケープに使えることが分かります。
########## # 例 7 ########## #!/bin/sh var='ls | wc -w' # ` ではなく '. echo "'$var' gives us the number of files: `ls | wc -w`"
実行例)
% sh script
'ls | wc -w' gives us the number of files: 6
(" 内では ' を解釈しない。`command` は解釈。)
4. スクリプト内のコメント
スクリプト内にコメント (注釈) を付けながらプログラミングすると,処理の流れが明確になります。コメントは,# を入力した行において,# 以降すべてが対象となります。
注釈: #
# # 例 8 # #!/bin/sh echo '# Example 8' echo This is an example. echo This is an ...# example.
実行結果) % sh script # Example 8 (# を ' でエスケープ) This is an example. This is an ... (# 以降無視される) %
5. 演算
- 整数上の演算:
expr 計算式和:+,差:-,乗算:*,除算:/,剰余:%
# # 例 9 # #!/bin/sh a=2 b=5 echo "$a + $b = `expr $a + $b`" echo "$a - $b = `expr $a - $b`" echo "$a * $b = `expr $a \* $b`" echo "$b / $a = `expr $b / $a`" echo "$b % $b = `expr $b % $a`"
実行結果) % sh script # Example 9 2 + 5 = 7 2 - 5 = -3 2 * 5 = 10 5 / 2 = 2 5 % 2 = 1 %
- 小数点,関数などの計算:コマンド
bc# # 例 10 # # 詳細は,コマンド
bc# のマニュアル (man bc) を参照 # #!/bin/sh echo '# Example 10' a=`echo "scale=5; e (1)" | bc -l` (出力を bc にパイピング) b=`echo "scale=5; sqrt (2)" | bc -l` pi=`echo "scale=10; 4*a(1)" | bc -l` echo $pi echo "$a + $b = `echo $a + $b | bc -l`" echo "$a - $b = `echo $a - $b | bc -l`" echo "$a * $b = `echo $a \* $b | bc -l`" echo "$a / $b = `echo $b / $a | bc -l`" echo "$a % $b = `echo $b % $a | bc -l`"実行結果) % sh script # Example 10 3.1415926532 2.71828 + 1.41421 = 4.13249 2.71828 - 1.41421 = 1.30407 2.71828 * 1.41421 = 3.8442187588 2.71828 / 1.41421 = .52025913445266859926 2.71828 % 1.41421 = .0000000000000000000035272 %
6. if 文
シェルは,単に,UNIX のコマンド処理ということだけではなく,プログラミング言語としての一面があると感じるのが,「if文」の存在です。
■if 文 ([ ] 部分はオプション)
if 条件
then
処理1
[elif 条件]
[then]
[処理2]
[else]
[処理3]
fi
次の例は,例2のスクリプトにおいて,スクリプトを実行する段階で変数が代入されなかったとき,相手に尋ねるものです。
#
# 例 11
#
#!/bin/sh
echo '# Example 11'
message=$*
if [ ! -s $message ]
then
echo -n 'Enter your message: '
read message
fi
echo "Your message to your boss was: $message"
実行例1) % sh script "I'm drinking!" # Example 11 Your message to your boss was: I'm drinking! % 実行例2) % sh script # Example 11 Enter your message: Getting fat? Your message to your boss was: Getting fat? %
この例において,[ ] 内の -s は条件判定用の演算子。[ ] は条件評価の命令。
test コマンド:[condition ], test condition[ ]内,また,testに続く条件conditionを評価する。真ならば0,偽ならば1を返す。- 条件演算子
・文字列評価 string1 = string2 両者が同じ string1 != string2 上の反対 ・数値評価 num1 -gt num2 num1 > num2 num1 -ge num2 num1 >= num2 num1 -eq num2 num1 = num2 num1 -le num2 num1 <= num2 num1 -lt num2 num1 < num2 num1 -ne num2 num1 not = num2 ・ファイル検査 -r file file 読み込み可 -w file file 書き込み可 -x file file 実行可 -e file file 存在 -s file file が空でない -d file file はディレクトリ -f file file は通常のファイル ・論理演算 ! condition 条件 condition の否定 cond1 -a cond2 cond1 AND cond2 cond1 -o cond2 cond1 OR cond2
- [ノート] 省略形:
&&と||・command1 && command2 これは以下と同じ。 if [command1] then command2 fi ・command1 || command2 これは以下と同じ。 if [!command1] then command2 fi
7. case 文
代入された値とパターンマッチを行い,マッチした場合の処理をさせるのが「case 文」です。
■case 文
case 変数 in [値1] [値2] ...
パターン1)
処理1
;;
パターン2)
処理2
;;
esac
[値1] [値2] ... 部分は,変数に値が代入されていない場合に指定。また,パターンは幾つでも良い。パターン部分には,正規表現が使用可能。また,| で OR を意味する。
# # 例 12 # #!/bin/sh echo '# Example 12' echo -n 'Do you want to run hello.pl? [y/n]: ' read answer case $answer in y*|Y*) <-- パターンマッチに正規表現が使用可能。| は OR の意。パイプではない。 echo 'Running hello.pl...' perl ~/My_Programs/hello.pl exit <-- スクリプトの処理を終了させる。 ;; n*|N*) echo 'Sorry.' exit ;; esac echo "Uuuhm...You didn't enter y[es] or n[o]."
実行例) % sh script # Example 12 Do you want to run hello.pl? [y/n]: yes Running hello.pl... Hello World! Message %
8. for 文
シェルの for文は,繰り返し処理に使う値を明示します。
■for 文
for 値 in 値1 値2 ...
do
処理
done
「値1 値2 ...」の部分には,$* (標準入力) や `command` (コマンドの標準出力) を代入することも可能。また *txt とすれば拡張子 .txt のファイルすべてに対し同じ処理ができます。
# # 例 13 # #!/bin/sh echo '# Example 13' echo $# inputed echo '+------------+' for i in $* do echo $i done echo '+------------+' for i in "$*" do echo $i done echo '+------------+' for i in "$@" do echo $i done
実行例) % sh script a b c 'd e f' # Example 13 +------------+ 4 inputed +------------+ a b c d e f +------------+ a b c d e f +------------+ a b c d e f %
[ノート]引数は4つと読んでいるが,$* には6つ入っている。シェルはメタ文字を取り除き,スペースで区切りられた部分を分解し,それを $* に格納するのである。そこで,ダブル・クウォーテイションで $@ を囲めば,4つになる。ちなみに,$@ をダブル・クウォーテイションで囲まないと $* と同じ結果となる。また,a b c 'd e f' の間のスペースは幾つ入れても同じ結果となる。
9. while 文
指定した条件が満たされる限り,同じ処理を繰り返します。
■while 文
while 条件
do
処理
done
# # 例 14 # #!/bin/sh echo -n 'Enter your favorite integer: ' read num i=0 while [ $i -le $num ] do echo $i i=`expr $i + 1` done
実行例) % sh script Enter your favorite integer: 167 0 1 2 3 . . . 166 167 %
10. until 文
while 文は,指定した条件が満たされる限り処理を繰り返しますが,逆に,指定した条件が満たされるまで処理を繰り返すのが until 文です。
■until 文
until 条件
do
処理
done
# # 例 15 # #!/bin/sh echo -n 'Enter your favorite number: ' read num echo "Sleeping $num seconds..." i=0 until [ $i -ge $num ] do echo -n '...' i=`expr $i + 1` sleep 1 echo $i done
実行例) % sh script Enter your favorite number: 7 Sleeping 7 seconds... ...1 ...2 ...3 ...4 ...5 ...6 ...7 %
11. ループから抜ける:break
for, while, until 文は,繰り返し作業(ループ)を行います。それは,指定した条件が満たされる,あるいは,満たされない場合に繰り返しますが,時として,その繰り返し作業を止めさせ,次の作業に移りたい場合もあるでしょう。このような「ループから抜ける」ための命令文が,break です。
■break n ループ制御の内側から n 番目までを省略。 n を省略した場合,最も内側のループを抜ける。
#
# 例 16
#
#!/bin/sh
echo -n 'Enter your favorite number: '
read num
echo "Sleeping $num seconds..."
i=0
until [ $i -ge $num ]
do
echo -n '...'
i=`expr $i + 1`
sleep 1
echo $i
if [ $i -ge 10 ]
then
echo "Hurry up!"
break
fi
done
実行例) % sh script Enter your favorite number: 60 Sleeping 60 seconds... ...1 ...2 ...3 ...4 ...5 ...6 ...7 ...9 ...10 Hurry up! %
12. ループ中の作業の一部を飛ばす:continue
for, while, until 文では,do と done の間に幾つかの作業を記述します。しかし,時として,ある条件の下では,すべての作業をさせる必要がないときもあります。ループ内の作業の一部のみをさせ,ループ内の次に飛ばすのが continue です。
■continue n
ループ制御の内側から n 番目までの後の処理を省略。 n を省略した場合,最も内側のループの処理を指定したところから飛ばす。
#
# 例 17
#
#!/bin/sh
echo '# Example 17'
echo 'Searching executables...'
for i in *
do
if [ ! -x $i ]
then
continue
fi
echo "===$i==="
done
実行例)
% sh script
# Example 17
Searching executables...
===MAIL===
===example.sh===
===hello.out===
%
13. 関数
同じ処理を何度となくさせる場合,その処理を「一つの関数」として設定し,スクリプト内でそれを呼び出せばその処理がいつでもできる方が,プログラムとしては簡明になります。この考え方を拡張すれば,1つ1つの小さな,しかし結果に意味・目的を持たせるような処理を「1つの関数」として設定し,関数の羅列でプログラムを組めば,プログラムは,飛躍的に見やすくなることでしょう。ここでは,先ず,数学の関数の考え方を復習し,その後,それをシェル・スクリプトでの関数の設定方法と呼び出し方を見ます。
13.1. 数学の関数
変数 x にある値を代入すると,変数 y がある一定のルールの下にある値をとるとき,変数 y は変数 x の関数といい,その依存関係を y=f(x) などで表記し,変数 x を関数 f の独立変数,変数 y を従属変数と呼ぶのでした。独立変数 x はベクトルでもよいし,変数 y もベクトルかもしれません。関数の値(y の値)が,変数 x の任意の値に対し一定の場合,これも関数であり,「定値関数」と呼ばれるのも習ったことでしょう。通例,x の値が変われば,「一定のルール」に従い,y の値は変化します。
- 例:大小大きい方を出す関数
f(x1, x2) = max{x1, x2}
例えば,x1 = 1, x2 = 2 ならば,f(x1, x2) = 2.
13.2. シェルでの関数
上の「一定のルール」を「定めた処理」と置き換えてみましょう。然らば,プログラミングにおける関数は,「予め処理を定める」ことで,関数に渡された値(入力)がその処理に従い,他の値(出力)に変換される仕組みということになります。シェルでの関数の定義方法,およびスクリプト内での変数の代入方法は,以下の通りです。
- 関数の定義
■関数の定義 関数名 () { 処理 }但し,関数は,それを呼び出す前に定義されていなければなりません。
- 関数の独立変数と呼出し方法
関数は,スクリプト内の変数を独立変数に持てます。しかし,特定の値を関数に代入したい場合,スクリプト内で次のように関数を呼び出せば良いです。
■関数の呼出し 関数名 値1 値2 ...
関数内では,値
xを$x($1,...,$9, または $*) で参照できます。もちろん,$#も使えます。 - 関数の従属変数
関数内で処理された結果(変数の値)は,すべてスクリプト内で参照できます。すなわち,関数内のすべての変数値が数学でいう従属変数です。しかし,次のようにすれば,ある特定の値のみを関数の値とすることができます。
関数名 () { 処理 return 従属変数 }# # 例 18 # #!/bin/sh ### Function: f_max ### f_max () { if [ max -lt $1 ] then max=$1 fi } ### MAIN ### echo '# Example 18' num=$* if [ -z $num ] then echo -n 'Enter your favorite integers: ' read num fi set $num max=$1 shift while [ $# -gt 0 ] do f_max $1 shift done echo "Maximum: $max"実行例) % sh script # Example 18 Enter your favorite integers: 1 85 47 2 5 9 56 32 24 11 93 Maximum: 93 %