[Litz' UNION] > [Dal Segno] > [UNIXたん] > [その9] // [その8][その10]

UNIXたん その9

シェルスクリプト

シェルスクリプトは、スクリプトファイルのコマンド化を使って、一連の処理を1つのコマンドのように扱うものです。
複雑な作業をシェルで何回も行う時などには非常に有効な手段となるでしょう。

シェルスクリプトの文法は、sh系とcsh系で大きく異なります。cshでは簡単にスクリプトを組むことができる一方で文法的な問題点があるため、最初のうちはお勧めできない。
ここでは、sh系シェル(特にbash)について学習していきます。

シェルスクリプト作成の手順
(1) スクリプトファイルを置くディレクトリを作る。通常は「bin」
(2) (1)で作ったディレクトリにパスを通す。.bashrc で設定
(3) シェルスクリプトを実際に書き、(1)のディレクトリに置く。
(4) スクリプトファイルに実行許可を与える。

(1) cd ~; mkdir bin
(2) .bashrc に以下の内容を記述する。

if [ -d $HOME/bin ]
then
    PATH=$PATH:$HOME/bin; export PATH
fi

(4) chmod u+x ./sampleなど。

シェルスクリプトの基本構造

スクリプトファイルのコマンド化と大体は同じこと。
先頭行に何を書くかというのが焦点になってきますが、ここは何も言わずに
#!/bin/sh
と書いておきませんか

また、shにオプションをつけて以下のように動作させることが可能。

#!/bin/sh -n
スクリプトの内容は実行せず、文法チェックのみ行います。

#!/bin/sh -v
実行すると同時に、スクリプトの内容を標準エラーに出力します。

また、コマンドの連続実行について。
シェルの機能として解説しましたが、今一度まとめておきます。

コマンド1;コマンド2 … 1の後に2を実行
(コマンド1;コマンド2) … 1,2をサブシェルで実行(状態を一時的に変更)
コマンド1&&コマンド2 … 1が成功すれば2も実行する
コマンド1||コマンド2 … 1が失敗すれば2も実行する

シェルスクリプトの文法

コメントアウト
シェルスクリプトでは、「#」から行末がコメントとして扱われます(文字列中を除く)。

変数
「変数名=値」と書くことで変数の宣言と代入が行えます(スペースを入れてはならない)。
文字列
「'」で囲んだ文字列は、何が書かれていようが単なる文字列として扱われます。
「"」で囲んだ文字列は、「$」「`」「\」を評価します。「\$」などとすると単なる文字。

#!/bin/sh

g='Hello, world!'
d="today is `date +%m/%d`"
echo $g, $d.

このようなスクリプトを作ってみましょう。
gは単なる文字列、dでは先読み評価(`date...`)がなされています。

引数
シェルスクリプトでの引数の扱いを以下に。
$# … 引数の個数
$0 … シェルスクリプト自身の名前
$n … n番目の引数。nが2桁の場合は${n}
$* … 引数全て
${m}-$[n] … m番目からn番目の引数

#!/bin/sh

uid=`whoami`
name=`(finger | awk /$uid/'{print $2}')`
g="($1 says) Thank you, $name"
echo $g.

このようなスクリプトを作ってみましょう。
Welcome nanashi などして引数の扱いを確認のこと。

入出力
シェルスクリプトが実行中に入出力を行うには、基本的に次のコマンドを使います。
read 変数 … ユーザからの入力を待ち、結果を変数に入れる。
echo なんたらかんたら … 出力。
echo -n なんたら(ry … 出力後に改行しないバージョン。

#!/bin/sh

g='Hello'
i='Please tell me your name: '
o="Oh, nice to meet you"
echo $g.
echo -n $i
read ans
echo $o, $ans.

こんなスクリプトで実感してみて下さい。

計算
数値計算をするには、先読み評価を用いてexprやbcを呼び出すしかない(それほどでも…)。
単純な計算ならexpr、少々面倒な計算ならbc、といった使い分けで良いでしょう。

#!/bin/sh
a=5
b=`expr $a + 10`; echo $b
c=`expr $b \* 2`; echo $c     #単に「*」だとファイル名展開される虞あり


#!/bin/sh
a=`echo "4*a(1.0)" | bc -l`; echo $a     #4*arctan(1)=π
b=`echo "c($a)" | bc -l`; echo $b     #cos(π)

関数

sh系シェルでは、シェル内で関数(サブルーチン)を作成することができます。
使い方はエイリアスに似てる。構文は以下の通り。

関数名 () {
    コマンド
    コマンド…
}

関数に対するn番目の引数を$nとして参照できます。
また、関数内部だけで用いる変数を宣言するときは、変数名の前にlocalを付けて。

#!/bin/sh
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 "Hello, about $min minutes have passed this month."

制御構造

条件分岐
if 条件1
  then 処理1…
elif 条件2
  then 処理2…
elif 条件3
  then 処理3…
else 処理0
fi

条件1が実行され、OKなら処理1を行って終了。
そうでなければ条件2を実行し、OKなら処理2を行って終了。
そうでなければ条件3を…(ry
最後までOKが出なければ、処理0を行って終了。
※elif...then...のセットは、いくつでも構いません(無くても良い)
※else...は、無くても構いません。

条件分岐(パターンマッチ)
case 文字列 in
  パターン1 ) 処理1… ;;
  パターン2 ) 処理2… ;;
  * ) 処理0… ;;
esac

文字列を評価し、パターン1とマッチすれば処理1を行って終了。
そうでない場合、パターン2とマッチすれば処理2を(ry
最後までマッチしなければ、処理0を行って終了。
※パターン ) 処理 ;; は、いくつでも構いません。
※ * ) ...は、無くても構いません。

繰り返し(リストの要素毎)
for 変数 in パターンあるいはリスト
do
  処理…
done

パターンが指定された場合、まずはこれを展開してリストを生成。
リストの要素を順に変数に代入して処理を行う。

繰り返し(条件)
whileあるいはuntil 条件
do
  処理…
done

「条件式を実行し、OKなら(untilの場合はNGなら)処理を行う」の繰り返し。
※無限ループを作ってしまわないように注意。

その他
break … 最も内側のfor, while, untilループからの脱出。
exit … シェルスクリプトの終了。

#!/bin/sh
fn=~/.bashrc
if [ -e $fn ]
  then cat $fn
else echo "You have no $fn."
fi


#!/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 "What is $fn?"
esac


#!/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


#!/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

条件のチェック

条件は基本的に [ 条件式 ] と書きます。
[ の後ろにはスペースが必要なので注意( [ というコマンドらしい)。
条件式の書き方は、以下に列挙した通り。

[A}ファイルテスト
-e ファイル名 … ファイルが存在すれば真、そうでなければ偽
-d ディレクトリ名 … ディレクトリが存在すれば真、そうでなければ偽
-f ファイル名 … 通常ファイルが存在すれば真、そうでなければ偽
-r ファイル名 … 読み込み可能なファイルならば真、そうでなければ偽
-w ファイル名 … 書き込み可能なファイルならば真、そうでなければ偽
-x ファイル名 … 実行可能なファイルならば真、そうでなければ偽
-s ファイル名 … サイズが0より大きなファイルならば真、そうでなければ偽

[B]文字列
-z 文字列 … 長さ0の文字列(空文字列)ならば真、1以上ならば偽
-n 文字列 … 長さ1以上の文字列ならば真、0(空文字列)ならば偽
文字列a == 文字列b … 文字列が一致すれば真、異なれば偽
文字列a != 文字列b … 文字列が異なれば真、一致すれば偽

[C]数値
数値a -eq 数値b … 数値が等しければ真、異なれば偽
数値a -ne 数値b … 数値が異なれば真、等しければ偽
数値a -gt 数値b … aがbより大きければ真、b以下ならば偽
数値a -ge 数値b … aがb以上ならば真、bより小さければ偽
数値a -lt 数値b … aがbより小さければ真、b以上ならば偽
数値a -le 数値b … aがb以下ならば真、bより大きければ偽

[D]その他
!条件式 … 条件式が成り立たなければ真、成り立てば偽(否定)
条件式a -a 条件式b … 両方とも成り立てば真、いずれかが成り立たなければ偽(論理積)
条件式a -o 条件式b … いずれかが成り立てば真、いずれも成り立たなければ偽(論理和)
( 条件式 ) … 条件式をグループ化する。

[Litz' UNION] > [Dal Segno] > [UNIXたん] > [その9] // [その8][その10]