最終更新日 …
$Date: 2003-05-19 01:35:30+09 $
シェルスクリプトとは
スクリプトファイルのコマンド化
を利用して,シェルの複雑で連動するコマンドをあたかも一つのコマンドのように扱えるようにするものである.
シェルで何回も似たような作業をするときなどには,非常に威力を発揮するので,
面倒な作業だなと思ったら,シェルスクリプトを組めないか検討してみるのが良い.
シェルスクリプトの文法に関しては,sh 系シェルと csh 系シェルで結構な違いがある.
csh 系シェルの方が人間にとって直感的な文法を採用しているので簡単にスクリプトを作成できる傾向がある.
しかし,
有害な csh プログラミング
などでも指摘されているように csh では文法的な問題点がいくつかある.
また,sh はどのような unix でもほぼ必ず存在するが csh はそうとは限らないため,
シェルスクリプトの可搬性を考えると sh 系の方が良い.
注意
こうした事情から,本講義では sh 系シェルスクリプトについて学習する(これは正統的である).
もし余裕があるようならば csh 系のシェルスクリプトについても学んでみると良いだろう.
シェルスクリプトは便利なものなので,
一度作ったら後で何度も利用することになることが多い.
そこで,作ったシェルスクリプトは後からでも簡単に使えるようにしておくのが良い.
それには,シェルスクリプトをまとめて置いておくディレクトリを用意して,
そのディレクトリに path を通す,という手順を踏んでおくことになる.
また,新しくシェルスクリプトを作成したら,そのファイルを実行できるよ
うにパーミッションを設定しておかないといけない.
以上を具体的に書いてみると次のようになるだろう.
cd ~
mkdir bin
if [ -d $HOME/bin ] then PATH=$PATH:$HOME/bin; export PATH fiなどと書き込んでおけば,次回ログイン時よりこれが有効になる.
source ~/.bashrc
とした方が簡単なのは… 覚えているか?
chmod u+x ./test
などとすれば良い.
実習
上に示した 1, 2 を実行しておこう.
スクリプトファイルのコマンド化
を用いてシェルスクリプトは作成される.
問題は,スクリプトの先頭行に何と書くかである.
シェルスクリプトはシェル設定ファイルを読まないようにした方が安定して動作すること,
なるべく普遍的な環境で動作すること,非対話環境であること,
を考慮すると,結論から言えば
#!/bin/bash --noprofile --posix
と書くか,
#!/bin/sh
と書くのが良いだろう(この二つは基本的に同じ動作になる).
間違いの少なさや普遍性から言えば,後者の表記が良さそうだ.
また, sh にオプションをつけて,次のようにすることもできる.
場合に応じて使うと良い.
実習
#!/bin/sh
echo "Hello, world!"
Hello
と打ち込んで,作成したシェルスクリプトが動作することを確認する.
シェルではコマンドを連続実行したり,グループ化できたりするのは初期の授業の シェルの機能 の部分で学習したが, シェルには連続するコマンドに対する他の機能もある. ここでそれらをまとめておく.
文法 | 解説 |
---|---|
コマンド1 ; コマンド2 | コマンド1 を実行後,コマンド2 を実行する. |
( コマンド1 ; コマンド2 ) |
コマンド1 を実行後,コマンド2 を実行する… のだが,
これら二つのコマンドを「サブシェル」のもとで実行する(グループ化).
サブシェルで行った環境変更はもとのシェルに影響しない.
よって,状態を一時的に変更して作業するときに便利.
例) 例えば, (cd /tmp; ls -a)
とすると,/tmp で作業が行われるが,終わったあとにディレクトリは変更されていない.
|
コマンド1 && コマンド2 | AND 実行. コマンド1 を実行してみて,コマンド1 が成功したならば(エラーにならない時), コマンド2 を実行する. |
コマンド1 || コマンド2 | OR 実行. コマンド1 を実行してみて,コマンド1 が失敗したならば(エラーになった時), コマンド2 を実行する. |
シェルスクリプト中では,# という文字があると,
そこから行末までがコメントとして取り扱われる(ただし,文字列中は除く).
コメントを適切に書き込むことで,よりわかりやすいスクリプトを作ることができるだろう.
シェルスクリプトも一種のプログラムであるから,
変数を使いたいというのは当然である.
そこで,変数をどう使うかであるが,文法的には
シェルでの変数の設定
の「シェル変数」の部分を参照すれば良い.
基本的には,
変数名=値
とすることで,変数の宣言と代入が同時に行える.
注意
このとき,変数名と「=」の間にスペースを入れると,
その名前のコマンドを実行するのだと解釈されてしまう(誤動作)ので注意せよ.
そして,その変数の内容を使いたい(参照)時は,
$変数名
とすれば良い.この時,
${変数名}
とした方がより安全に参照できる.
似た名前の変数がある時や,二桁番目の引数変数などはこうして参照するのが良い.
中カッコで囲むことによって問題が起きることはあまりないので,
いつでもこうしておく,という方が良いかもしれない.
シェルスクリプトの中で文字列を文字列だ,と表すには quotation しないといけない.
そうしないと変数とみなされてしまう.
この時,
「'」(シングルクォート)
で囲むのと,
「"」(ダブルクォート)
で囲むのでは性質がやや異なるので注意が必要だ.
実習
先の Hello スクリプトを次のように改造しよう.
#!/bin/sh
# Greeting Script
g='Hello, world!'
d="today is `date +%m/%d`"
echo $g, $d.
何がどうなっているのか理解せよ.
引数とは,プログラムを起動するときに,一緒に与える指示子をいう…
と書いても何が何だが, 例えば
ls -a /tmp
とプログラムを実行したら,広い意味で引数は
-a, /tmp
の二つである(オプションを引数に含めるかどうかは定義次第だ).
シェルスクリプトはその性質上,引数をともなって実行されることが多い.
そこで,引数の取り扱いはきちんとできないといけないだろう.
次に引数がどう取り扱われるかを示す.
変数 | 意味 |
---|---|
$# | 引数の個数 |
$n |
n 番目の引数.
ただし,0番目の引数 $0 は特別で,
シェルスクリプト自身の名前になる.
また,n > 9 である場合は ${12} などと,中カッコで囲まないと使えない.
|
$* | 引数全て. |
${m}-${n} | m 番目から n 番目までの引数. |
また,
shift
というコマンドを実行すると,
$1 に $2 の内容が入り,
$2 に $3 の内容が入り… と
引数が一つずつずれる(このとき,$# は 1 減る).
古めのスクリプトではこの
shift
というコマンドはよく使われている.
実習
Welcome というファイル名でスクリプトを次のように作る.
#!/bin/sh
# Welcome Script
uid=`whoami`
name=`(finger | awk /$uid/'{print $2}')`
g="($1 says) Thank you, $name"
echo $g.
そして,このスクリプトを次のように実行してみよう.
Welcome smith
実行してみるとともに,
何がどうなっているのか理解せよ.
シェルスクリプトが実行中に入出力を行うには,主に次のコマンドを用いる.
# もちろん,ひねくれた方法はいくらでもありうるだろうが…
コマンド | 解説 | 例 |
---|---|---|
read 変数 |
入力.
ユーザからの(キーボード)入力を待ち,その結果を変数にいれる. |
#!/bin/sh
を実行するとどうなるだろうか.
|
echo 出力したいもの
echo -n 出力したいもの |
出力.
-n オプションがついていると,出力後に改行しない. |
実習
Hello スクリプトをさらに改造して次のようにしよう.
#!/bin/sh
# Greeting Script
g='Hello'
i='Please tell me your name: '
o="Oh, nice to meet you"
echo $g.
echo -n $i
read ans
echo $o, $ans.
そして,このスクリプトを実行してみるとともに,
何がどうなっているのか理解せよ.
オリジナルの sh は原則として計算機能を持っていない(bash は実は計算機能を持っているのだが).
そこで,シェルスクリプト中で計算を行いたいときは,互換性も鑑みて,
バッククォーテーションを用いた
先読み評価 ``
を利用して外部コマンドを呼び出す手法を使うことになる.
具体的には,
expr
や
bc
を使って,次のように行うことになる.
簡単に言って,簡単な整数計算だけでよければ
expr
を,やや複雑な実数計算を行いたければ
bc
を使う,ということになる.
expr については,マニュアルを参照してもらうとして,
使い方は以下のように簡単なので問題ないだろう.
#!/bin/sh
← ` ` の中では * は展開されてファイル名などになってしまいかねないので,\ で単なる文字としておく.
a=5
b=`expr $a + 10` ; echo $b
c=`expr $b \* 2` ; echo $c
実習
上のスクリプトを試してみよ.
bc は,標準入力から計算式をもらうことで計算を行う機能があるので,
echo でリダイレクトすることでそれを利用する.
具体的には次の例をみれば良いだろう.
#!/bin/sh
← πを計算している.
a=`echo "4*a(1.0)" | bc -l` ; echo $a
← cos(π)を計算している.
b=`echo "c($a)" | bc -l` ; echo $b
実習
上のスクリプトを試してみよ.
sh 系シェルには,そのシェル内で関数を作成することができる.
関数という名前がついているが,コンピュータ言語的には手続き(procedure)とか,サブルーチンとかいう方がわかりやすいだろう.
要するに,コマンドをひとまとめにして名前をつけたものである.
# 全てのルーチンを関数として扱う C 言語流になっている.unix だから当たり前とも言える(^-^).
構文 | 説明 | 例 |
---|---|---|
関数名 () {
注意 関数に対する n 番目の引数を $n
として参照できる.
また,この関数内部だけで使いたい変数があるときは, 変数宣言の時に local
を頭部につける.
|
複雑な手続きをひとまとめにすることができる.
引数変数が使えるので, 引数を持てない alias の代わりに使える. |
というシェルスクリプトはどういう動作をするだろうか?
(文法的に分からない部分は,この web を最後まで読めばわかるはずだ) |
実習
Hello スクリプトをさらに改造して次のようにしよう.
#!/bin/sh
# Greeting Script
g='Hello'
d=`date +%d`
dm=`expr $d - 1`
t=`date +%H`
m=`date +%M`
d2min () {
min=`echo "(($1 * 24) + $2) * 60 + $3" | bc -l`
}
d2min $dm $t $m
echo $g, "about $min minutes have passed this month."
そして,このスクリプトを実行してみるとともに,
何がどうなっているのか理解せよ.
(algol系)プログラムには,「もし…ならば」とか,「…を繰り返して…」 という動作が必要になる. これらの動作を実現するためのコマンドや構造を制御構造という. sh 系シェルスクリプトでは,次のような制御構造がある.
構文 | 動作の流れ |
---|---|
if 条件1
注意
if の後の条件については,
条件式および条件式のチェック
を参照せよ.
注意 elif 以下と else 以下は存在しなくても良い. |
最初に 条件1 が実行 & チェックされ,
条件1 が成り立つ(= 実行が成功する) → コマンド1… が実行されて if 文は終了する. 条件1 が成り立たない → 次の elif へ. → 次の elif に来た場合.条件2 がチェックされ, 条件2 が成り立つ → コマンド2… が実行されて if 文は終了する. 条件2 が成り立たない → 次の elif へ. (… 以下,繰り返し …) → else に来た場合.無条件で コマンドN が実行されて if 文は終了する. |
if
のすぐ後の
[ -e $fn ]
という部分については後述するので理解は後回しでよい.
#!/bin/sh
fn=~/.bashrc
if [ -e $fn ]
then
cat $fn
else
echo "You has no $fn."
fi
構文 | 動作の流れ |
---|---|
case 文字列 in
*) は存在しなくても良い.
|
最初に 文字列 を評価して,
文字列がパターン1 とマッチする → コマンド1… が実行されて case 文は終了する. マッチしない → 次のパターンとマッチするか(以下繰り返し) → * ) に来た場合.無条件で コマンドN が実行されて case 文は終了する. |
#!/bin/sh
echo -n "Please input file name: "
read fn
case $fn in
*.txt )
echo "$fn is text file.";;
*.c )
echo "$fn is C program file.";;
* )
echo "Hey, what is $fn ? You know it?";;
esac
構文 | 動作の流れ |
---|---|
for 変数 in パターンorリスト
|
まず,パターンorリストを展開して,リストを生成する.
そのリストの要素を順番に 変数 に代入して, そのたびにコマンド… を実行する. |
#!/bin/sh
for x in 0 1 2 3 4 5 6 7 8 9 10
do
v=`echo "$x * 0.31416" | bc -l`
echo "sin($v) = " `echo "s($v)" | bc -l`.
done
構文 | 動作の流れ |
---|---|
while 条件
until の構文も同じ.
|
まず,条件が成り立つかチェック.
[ while の場合] 成り立つならば,コマンド… を実行. [ until の場合] 成り立たないならば,コマンド… を実行. 再び,条件が成り立つかチェック. 以下,繰り返し. |
while
のすぐ後の
[ $x -le 10 ]
という部分については後述するので理解は後回しでよい.
#!/bin/sh
x=0
while [ $x -le 10 ]
do
v=`echo "$x * 0.31416" | bc -l`
echo "sin($v) = " `echo "s($v)" | bc -l`.
x=`expr $x + 1`
done
sh 系シェルでは,条件は基本的に
[ 条件式 ]
という形式で書かれる.
注意
" [ " と条件式の間にはスペースが必要なので注意すること.
# 詳しく言うと,[ は条件式をチェックする,という機能を持つコマンドである.
本来の名前は test
である.
どうなっているのかは,
ls -lg `which [`
などとしてみると分かるだろう.
条件式の書き方は,基本的に「単項演算子」か「二項演算子」
と項目を組み合わせるというものである.
以下に列挙しよう.
実習
制御構造 if のところの実習例などを参考にして,上の条件を全部試すスクリプトを作成してみよ.
実習
制御構造 if のところの実習例などを参考にして,上の条件を全部試すスクリプトを作成してみよ.
シェルは条件式の判定には整数しか用いることができないので,注意すること.
実習
制御構造 if のところの実習例などを参考にして,上の条件を全部試すスクリプトを作成してみよ.
NOT
を表す.
条件式が成り立たなければ真.
AND
を表す.
両方の条件式が成り立てば真.
OR
を表す.
どちらかの条件式が成り立てば真.
実習
制御構造 if のところの実習例などを参考にして,上の条件を全部試すスクリプトを作成してみよ.
以下に示された課題について
AppliedMath-Report-07
という題名をつけて e-mail にて教官宛にレポートとして提出せよ(教官のメールアドレスは授業中に口頭で伝える).
なお,レポートを e-mail の代わりに TeX で作成した書面にて提出してもよい.
課題内容