最低限 UNIX / Linux [III] 【シェルスクリプト】

# 最低限 UNIX / Linux [III]

シェル
[1.1] シェル (Shell) とは
[1.2] シェルの役割
[1.3] 代表的なシェルの紹介
[1.4] シェルの確認と変更
◎[1.5] bash の機能
○[1.6] 環境のカスタマイズ (Bash)
[1.7] その他のシェル

最低限 vi
[2.1] vi とは
◎[2.2] ファイルオープン/クローズ
◎[2.3] vi の基礎 - 状態遷移
◎[2.4] 実践！訓練
[2.5] vi の基礎 - コマンドモード
[2.6] vi の基礎 - 挿入モード
A 参考 [vi の応用]

シェルスクリプト
◎[3.1] シェルスクリプト入門
○[3.2] シェルスクリプト応用
A 参考 [特殊記号の復習]

◎本日の課題
課題
ヒント
投稿の前に

<< 情報実験第4回トップへ

## 3. シェルスクリプト

さて, vi の使い方を大雑把に理解したところで
今日のメインである シェルスクリプト
について学習しましょう.

### ◎ [3.1] シェルスクリプト入門

これまで見てきたように, シェルはユーザとコンピュータの橋渡しをして,
ユーザがコマンドを端末から打ち込む毎に, それを解釈し実行します.
一方, [1.2]節で触れたように,
「プログラムが可能である」という, もう一つの面もあります.
この, プログラムとして手続きを書き込んだファイルを
「スクリプト・ファイル」と言います.
スクリプト(script)とは劇の「台本」のことであり,
台本を事前に決めていてそれに沿って行わせるためにこの名前があります.

シェル以外にも多くのスクリプト言語, 例えば Perl, Ruby などがあります.
もちろん全部を覚える必要は全くありませんが,
一つでも使いこなせると非常に便利です.
機械的な作業を膨大に繰り返す場合, あるいは普段頻繁に行う一連の作業は
機械に作業の手順を教えて人間は楽をするのが賢い方法です.
すぐにこういった膨大な作業をする必要にせまられることは無いかも知れませんが,
早いうちに慣れておくと, いざ必要になったとき楽なものです.
ここでは Linux で標準シェルである
Bash を使ったシェルプログラミングを学習することにします.

具体的にどのようなものか, 簡単な例を見てみましょう.

次のような一連の仕事を行うコマンドを作ることを考えます.

日付・時刻を表示する.
自分の名前を表示する.
自分のログインシェルを/etc/passwd から調べて変数に代入する.
変数の内容を表示する.
ユーザにかけ声をかけて励ます.

それぞれの働きをするコマンドは

date
echo $USER
(注: $USER はユーザ名を示す環境変数([1.6.2]参照) )
loginname=`grep ^$USER: /etc/passwd | cut -d: -f 7`
(注: 「cut コマンド」 各行から特定の部分を抽出する. 詳しい使用法を知りたい場合は man で調べよう.)
(注: コマンドを括る引用符がシングルクォーテーション「'...'」や
ダブルクォーテーション「"..."」ではなく,
バッククォーテーション「`...`」であることには意味があります.
詳しく知りたい場合は
[3.2.6] 引用符を調べてみましょう).
echo $loginname

です(Hey! と言われて励まされるかどうかは議論の対象としない).

vi を使って次のようなファイルを作成しよう.

$ vi sample.sh
:

[スクリプトファイル：sample.sh]

date
echo "I am $USER."
loginshell=`grep ^$USER: /etc/passwd | cut -d: -f 7`
echo "My login shell is $loginshell"
echo "Hey $USER !!"

編集・保存したら,

$ bash sample.sh

とすると, 次のように出力される.

Fri Nov 9 06:19:57 JST 2002
I am mym.
My login shell is /bin/bash
Hey mym !!

この場合, bash をわざわざ起動して,
その引数としてファイル名を指定し,
そのファイルを bash が解釈して実行しています.

いちいちこのようなことをするのは非能率でもあるので,
以下の手順で先程のファイル(sample.sh)を実行可能なファイルにします.

ファイル sample.sh の先頭行に "#!/bin/bash"という
おまじないを書き込む.

- #!/bin/bash
date
echo "I am $USER."
loginshell=`grep ^$USER: /etc/passwd | cut -d: -f 7`
echo "My login shell is $loginshell"
echo "Hey $USER !!"
- ファイル sample.sh に実行パーミッションを加える.

- $ chmod 744 sample.sh
- もしくは
- $ chmod u+x sample.sh
- ls コマンドでパーミッションを確認.
- $ ls -lF sample.sh
-rwxr--r-- 1 inex inex 204 Oct 24 05:12 sample.sh*

所有者のパーミッションのところに, 実行権限である「x」がついている
ことを確認する.

こうすることでスクリプトのファイル名を入力するだけで
実行することができます.
※但し, PATH には注意して下さいね.

$ ./sample.sh

この場合, 自動的に(自分が使っている login シェル)が,
スクリプトファイルを bash に解釈させ実行させています.

### ○ [3.2] シェルスクリプト応用

#### [3.2.1] 引数処理

引数とは, 実行時にそのシェルスクリプトに教えて上げたい
追加情報であるとも言えます.

たとえば, コマンド ls は, そのままではカレント・ディレクトリ内の
ファイルをリストアップしますが, 引数として特定のディレクトリを指定すると,
指定されたディレクトリ内のファイルをリストアップします. これはコマンド
ls に, 引数として指定したディレクトリについて知りたいんだよ,
と追加情報を与えているわけです.

コマンドやシェルスクリプトに引数を指定することを,
"引数を渡す"とも表現します. シェルスクリプトに渡された引数を
処理したい場合は, $1, $2, ..., $9 というシェル変数を利用します.

例えば, 引数に渡された単語をそのまま表示する
シェルスクリプトを考えてみましょう. 次のようなシェルスクリプト(test.sh)を
作成してみてください.

$ vi test.sh

#!/bin/sh
echo $1

$ ./test.sh Hello!
Hello!

--> 引数に渡した Hello! を返す

ここでは, 引数の文字を画面に出力するコマンド echo に,
シェル変数 $1 を引数として渡しています. このシェル変数 $1 には,
シェルスクリプト test.sh に渡された引数 Hello! が入ります.
結果, 画面には Hello! と表示されるでしょう.

引数を参照するシェル変数は９つまで使うことが出来ます.
それぞれ, $1, $2, ... $9 です.
※引数のデフォルト値などを設定するには 文字列演算子等を利用します.

#### [3.2.2] 処理の流れを制御する - 順序構造

より複雑な処理をこなす場合でも, 大抵は次の３つの処理の組み合わせで表現できます.

順序構造
選択構造
繰り返し構造

順序構造は, 順番にコマンドを実行するものであるから, 特別の制御文は必要ありません.

選択構造には, if , case を使います. 繰り返し構造には, for , while , until を使います.

#### [3.2.3] 処理の流れを制御する - 選択構造

if
書式

if テストコマンド１
then コマンド１
[elif テストコマンド２
then コマンド２]
:
[else
コマンド３]
fi

解説

if 文は if と fi で対になって, いれ子を形成しています.
まず, if に続く「テストコマンド１」を実行し,
それが「真」(０以外の値)を返したら then に続く「コマンド１」が,
「偽」(０)を返したら elif に続く「テストコマンド２」が実行されます.
「テストコマンド２」が「真」なら「コマンド２」が, 「偽」なら
次の elif が…と続いていき, 全てのテストコマンドが「偽」なら
最後に else に続く「コマンド３」が実行されます.

elif と「コマンド２」, else と「コマンド３」の部分は省略可能です.
また, 「コマンド１」, 「コマンド２」…には, 複数のコマンドからなる
コマンド列を記述することも出来ます. if....fi のいれ子は多重に
重ねることが出来ます.

if (elif) に続くテストコマンドの文と then に続くコマンドの文は
普通改行します. どうしても１行にまとめて書きたければ,
if テストコマンド; then コマンド のようにセミコロンを挟みます.

case
書式

case 文字 in
パターン１)　　コマンド１ ;;
パターン２)　　コマンド２ ;;
・
・
esac

解説

パターンには, メタキャラクタ ?, *, [-] を使ったパターンマッチング機能が使えます. case
の左に指定される「文字」はパターン１から順次比較され, 一致するとそのパターンに対応するコマンド列(括弧以降)が実行されます.
コマンド列の終わりは２重のセミコロン;;で区切られます.

#### [3.2.4] 処理の流れを制御する - 繰り返し構造

while, until
書式

while コマンド; do
コマンド
done

解説

while は, while の左に書かれたコマンドが真(０以外の値)を返す限り, do と done
の間に書かれたコマンド(列)を実行し続けます.

while の代わりに until と書くと, その左に指定されたコマンドが偽(０)を返す限り, do と done
の間に書かれたコマンド(列)を実行し続けます.

#### [3.2.5] 処理の流れを制御する - 例

#### ■与えられた引数の数を表示する.

#!/bin/sh
case $#
in
0) echo '引数なし' ;;
1)
echo '引数一つ' ;;
2) echo '引数二つ'
;;
*) echo '引数三つ以上' ;;
esac

※ $# は与えられた引数の数を表すシェル変数です.

■引数に指定された複数のファイル名に対し, 実在する物のみファイル名を表示する.

#!/bin/sh
while test $# -gt
0
do
if test -f $1;
then
echo
$1
fi
shift
done

※ test
は, それに続く式を評価して値を返すコマンドです. 与えるオプションに応じて, 数値に関するテスト, ファイルの型に関するテスト, 文字列に関するテストを行うことが出来ます. 例えば, test
-f [ファイル名]とすると, ファイルが存在すれば真, 存在しなければ偽を返します.

■数字の１から９を表示する.

#!/bin/sh
number=1
while test $number -ne
10
do
echo
$number
number=`expr $number + 1`
done

※expr コマンドはその引数を１つの式として評価し, その結果を表示します. これを使うと UNIX
で簡単な計算を行い, シェル・スクリプトの中に算術演算処理を含めることが出来ます. なお, number
の式の右辺はクオーテーション(')ではありません. 注意して下さい. (次節参照)

#### [3.2.6] 引用符

シェルスクリプトを作成する際, 引用符を用いることがあります.
引用符には以下の3種類があり, それぞれ意味が異なります(bash).

引用符

記号 呼称 意味
'...' シングルクォーテーション 内部の文字列をそのままの文字列として返す.
"..." ダブルクォーテーション 内部に含まれる変数等 $,`...`,\ を解釈した結果の文字列を返す
`...` バッククォーテーション 内部にあるコマンド(群)を実行し, その標準出力を文字列として返す.

実際に, 使ってみて違いを確認してみましょう.

$ var=ls
$ echo '$var'
$var
$ echo "$var"
ls
$ echo `$var`
sample.sh test.sh

<-- シェル変数 var に ls を代入
--> '' 内の文字列をそのまま返す
--> "" 内の変数の値を返す
--> `` 内の変数をコマンドとして実行した結果を返す

より詳しい情報は man bash として bash
のマニュアルを参照することで得ることも出来ます.
分からないコマンドや単語は, 困ってないでどんどん調べようね.

それでは
>> 本日の課題
にチャレンジ!!
<< 戻る
(vi エディタの使い方)
次へ >>
(本日の課題)

### A 参考 [特殊記号のおさらい]

### シェルスクリプト内でも, シェルで用意された特殊な記号を利用することができます.
既に習ったものを含め bash で使用される特殊な記号を以下にまとめました.

#### [A.1] メタキャラクタ

シェルにはファイル名が全部わからなくてもファイル名のパターンで
検索できる機能があります.
このファイル名とパターンの照合に用いられる特殊な記号を
"メタキャラクタ"(ワイルドカード)と言います.
メタキャラクタを上手に用いると, タイプする文字数を減らすことができ,
効率的な入力が行えます.

代表的なメタキャラクタ

メタキャラクタ 意味 用例 用例の意味
* 任意の文字列を表す.

ls /dev/h*

ディレクトリ"/dev/"以下にある1文字目が"h"で始まるファイル全てを
表示せよ.
? 任意の１文字を表す. ?? は任意の２文字になる.

ls -d /???

ディレクトリ"/"以下にある3文字からなるディレクトリ全てを
表示せよ.
[ ] [ ] 内に含まれる文字にマッチする. 例えば [a-c]* は abc のいすれかで始まる任意の文字列を表す
. []内の先頭に"^"を付けると"否定"と意味になる.

ls -d /[a-c]*

ls -d /[^a-c]*

ディレクトリ"/"以下にある"a,b,c"のいずれかで始まる(始まらない)ディレクトリを表示せよ.
{ } { }内に含まれる文字列にマッチする. 例えば test.{pl,gif,f} は, test.pl test.gif test.f
と入力したことになる.

ls /dev/{hda,hdb}*

ディレクトリ"/dev/"以下にある"hda,hdb"で始まるファイル全てを
表示せよ.

#### [A.2] リダイレクションとパイプ

UNIX のコマンドは必ず標準入力と呼ばれる共通した入力の受け付け口と,
標準出力と呼ばれる共通した出力の生成,
標準エラー出力と呼ばれる共通したエラーメッセージの生成を行う機能が
備えられています.
複数のコマンドの標準入出力を繋ぎ合わせたり,
ファイルへ/からの出力を行うために使う記号は
リダイレクションとパイプ(パイプライン)と呼ばれます.

代表的な入出力リダイレクタ

記号 意味
| パイプと呼ばれる. program1|program2 とすると program1 の標準出力を program2 につなぐ.
> program >file とすると標準出力を file に切り換える.
>> >>file とすると標準出力を file に追加する.
2> program 2>file とすると標準エラー出力を file に切り換える.
< program <file とすると標準入力として file から読み込む.
<<string ヒア・ドキュメント. string と書かれた行が次に現れるまで, この後に続く行を標準入力にする.

#### [A.3] その他

入出力リダイレクションとパイプ

記号 意味
; コマンドの区切り. program1;program2 とすると, まず program1 が実行され, 次に program2 が実行される.
& ;と似ている. ただし, program1 の終了を待たない.
(...) ... のコマンド(群)をサブシェルのなかで実行する.
{...} ... のコマンド(群)をカレントシェルのなかで実行する.
$1, $2, etc $0...$9 はシェルファイルに対する引数に置換される.
$var シェル変数 var の値.
${var} シェル変数 var の値. テキスト部分と連結されたときの混乱を防ぐために ${var} を用いる.
\ エスケープ記号. c がシェルのメタ・キャラクタである場合, \c は c の特別な意味を打ち消し, 単なる文字としての c にする.
# 単語の先頭に # があると, その行に書かれた残りの内容はコメントになる.
var=value 変数 var への値 value の代入
p1 && p2 p1 を実行し, もしうまくいったら p2 を実行する.
p1 || p2 p1 を実行し, もしうまくいかなかったら p2 を実行する.

#### [A.4] 文字列演算子

#### 基本的に文字列演算子構文では,
操作を指示する特殊な文字列を変数と右ブレース(})の間に挿入する.
演算子が引数を取る場合は演算子の右側に挿入する.
文字列操作演算子最初のグループは変数の存在を評価し,
ある条件のもとでデフォルトの値を置換する.

文字列演算子

使い方 意味 目的 例
${varname:-word} varnameが存在しNULLでない場合,
その値を返す.それ以外の場合はwordを返す. 変数が定義されていない場合にデフォルトの値を返す ${count:-0}は, countが定義されていなければ0と評価される.
${varname:=word} varnameが存在しNULLでない場合, その値を返す.
それ以外の場合はvarnameにwordを設定して返す. 変数が定義されていない場合にデフォルトの値を設定する. ${count:=0}は, countが定義されていなければそれに0を設定する.
${varname:?message} varnameが存在しNULLではない場合, その値を返す.
それ以外の場合はvarnameのあとmessageを出力し,
現在のコマンドあるいはスクリプトを中止する
(対話型シェルではない場合). messageを省略すると
デフォルトで "parameter null or not set"が
出力される. 変数が定義されていない場合に発生するエラーをキャッチする. ${count:?"undefined!"}は, countが定義されて
いなければ, "count:undefined"を出力して終了する.
${varname:+word} varnameが存在しNULLでない場合, wordを返す.
それ以外の場合はNULLを返す. 変数の存在を評価する. ${count:+1}は, countが定義されていなければ
1("真"の意味)を返す.
${varname:offset}
${varname:offset:length} サブ文字列を展開する. offsetの位置からlength文字
の長さのサブ文字列を$varnameの値から取り出す.
文字の位置は0からカウントする.
lengthが省略された場合, offsetの位置から
$varnameの終りまでのサブ文字列が返される.
offsetが0より小さかった場合, 開始位置は
$varnameのおわりからカウントされる.
varnameが@の場合, lengthはoffsetを先頭とする
位置パラメータの番号になる. 文字の一部を返す(サブ文字列またはスライスという). countがfrogfootmanと設定されている場合,
${count:4}はfootmanを返し,
${count:4:4}はfootを返す.

<< 戻る
(vi エディタの使い方)
次へ >>
(本日の課題)

最終更新日：
2006/11/04(森川 靖大)
Copyright © 2006 inex