最低限 UNIX / Linux [III]

  1. 最低限 vi
    [1.1] vi とは
    [1.2] ファイルオープン/クローズ
    [1.3] vi の基礎 - 状態遷移
    [1.4] vi の基礎 - コマンドモード
    [1.5] vi の基礎 - 挿入モード
    [1.6] 実践編
    [1.7] vi の応用
  1. シェルスクリプト
    [2.0] 前回のおさらい
    [2.1]ユーザーインターフェースとしてのシェル
    [2.2]シェルスクリプト入門
    [2.3] シェルスクリプト応用
    A 参考

2. シェルスクリプト

[2.0] 前回のおさらい

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

[2.0.1] シェルってなんだっけ?

シェルとは, ユーザーから入力されたコマンドを解釈し, プログラムを起動するアプリケーションのことでした.

例えると直接は言葉の通じない相手(kernel と呼ばれるプログラム)と あなたがコミュニケーションをする際に必要となる こまごまとした準備や通訳などをこなしてくれる秘書or通訳??さんの様な役割を はたしてくれるのが "シェル" (shell)と呼ばれるプログラムです. 普段意識しないと存在に気づかないのですが, 実は非常に有能なプログラムです. 彼(or 彼女?)の上手な使い方を覚えることは UNIX の能力を最大限活用するのに不可欠です.

[2.0.2] シェルの役割

有能な"秘書"であるシェルには以下の役割があります.

  1. ユーザーインターフェース(コマンド・インタプリタとしてのシェル)
    • プロンプトの表示
    • コマンドの読み込み
    • コマンドの前処理(解釈)
    • コマンドの実行
    • 環境設定の道具
  2. プログラミング言語
    (スクリプトを解釈する論理的な制御機能としてのシェル)

[2.1]ユーザーインターフェースとしてのシェル

いままで御目にかかっていたシェルは, 上記 1 の役割を果たすべく ログインをすると端末の上にプロンプトと呼ばれる "joho77$" などといった 文字列を表示して, あなたからの命令をまっていたプログラムです. これはログインシェルと呼ばれ, 各ユーザがログインする度に起動され, ログアウトの際に終了します.

[2.1.1] 利用するシェルの確認と変更

第2回の講義にあった /etc/passwd ファイルの第7フィールドに書かれたシェルが 各ユーザのログインシェルとして使われます. 始めてアカウントをもらったホスト(計算機)では必ずしも自分が使い成れた シェルがログインシェルとなっていません. この場合, ホストに準備されている使い成れたシェルをログインシェルに 変更したほうが良いでしょう.

[2.1.2] 自分のログインシェルを調べる方法

$ finger username
$ set

[2.1.3] 自分のログインシェルの変更方法

$ chsh (または ypchsh)

次にログインするときに, 変更したシェルが起動されます. (管理者の設定により無理な場合があります) /etc/shells に記載されているもが変更可能なシェルだが, 実際にそのシェルが存在するのか確認しておくこと (実体が無いとログインできなくなる).

[2.2] シェルスクリプト入門

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

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

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

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

それぞれの働きをするコマンドは
です(Hey! と言われて励まされるかどうかは議論の対象としない).

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

[スクリプトファイル:sample]
 
date
echo "I am $USER."
echo 'My login shell is ' | tr -d '\012'
grep ^$USER: /etc/passwd | cut -d: -f 7
echo "Hey !!"
ここで,
$ bash sample
とすると, 次のように出力される.
 
Fri Nov  9 06:19:57 JST 2001
I am mym.
My login shell is /bin/bash
Hey mym !!

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

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

  1. ファイル sample の先頭行に "# !/bin/bash"という おまじないを書き込む.
     
    # !/bin/bash
    date
    echo "I am $USER."
    echo 'My login shell is ' | tr -d '\012'
    grep ^$USER: /etc/passwd | cut -d: -f 7
    echo "Hey !!"
    
  2. ファイル sample に実行パーミッションを加える.

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

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


ジョブ制御

vi での編集と共にいろいろな作業をおこないたい (たとえば書いているスクリプトを実行してみる) 場合等にシェルのジョブ制御を使うと便利な場合があります.

ジョブ(Job)とは, ユーザがコンピュータに行なわせる仕事の単位です.

似た意味を持つ言葉に, タスクとプロセスがありますが, この2つは共にマルチプロ グラミングの目的で, コンピュータがCPU(中央演算処理装置)に行わせる仕事の単位 です. ジョブは, 複数のジョブステップから構成されることがありますから, ジョブと タスクは一致するとは限りません.

bash には, 1つのシェルで複数のジョブを切替えながら, 並行して作業を行う機能 があります. これをジョブ制御と呼びます.

上記2種のジョブを制御するために, bash には fg, bg, jobs コマンド, そして & amp; が用意されています.

はじめフォアグランドジョブとして起動されたコマンドを, バックグランドジョブに 変更するには, (フォアグランドジョブが実行されているために)プロンプトが表示され ていない状態の端末で, Ctrl-z(コントロール・キーを押しながら z キーを押す)として, ジョブを中断させます .

$ vi 
^Z
[1]+  Stopped                 vi
$ xclock 
^Z
[2]+  Stopped                 xclock
 

< Ctrl-z を押します
> ジョブ番号[1]の vi が停止しました

次に jobs と打ち込み, 現在このシェルから実行中のジョブ一覧を表示させます.

$ jobs
[1]-  Stopped                 vi
[2]+  Stopped                 xclock
> ジョブ番号[1]の vi が停止中です
> ジョブ番号[2]の xclock が停止中です

xclock より前に vi が実行されていたので, 上のような表示になります. [ ]の 中の数字はジョブの番号を表します. +は "current job", - は"previous job"と呼ばれ, ジョブの切替え対象となる順番を表しています.

bg コマンドを用いると, フォアグランドジョブをバックグランドジョブに切り替え ることが出来ます. (bg の後に % ジョブ番号と入力)

$ bg %2
[1]+  Stopped                 vi
[2]-  Running                 xclock &
> ジョブ番号[1]の vi が停止中です
> ジョブ番号[2]の xclock が実行中です

逆に, バックグランドジョブをフォアグランドジョブに切り替えるには, fg コマン ドを用います. (ジョブ番号の指定の仕方は, bg コマンドと同様)

$ fg %1
vi の画面へ

[2.3] シェルスクリプト応用

[2.3.1] 引数処理

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

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

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

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

$ script Hello!
#!/bin/sh
echo $1

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

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

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

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

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

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

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

 if
書式
    if テストコマンド
        then コマンド1
       [else コマンド2]
    fi
  
解説

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

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

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


 case
書式
    case 文字 in
        パターン1)  コマンド1  ;;
        パターン2)  コマンド2  ;;
          ・
          ・
    esac
  
解説

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

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

 while, until
書式
    while コマンド; do
      コマンド
    done
  
解説

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

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

[2.3.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 [ファイル名]とすると, ファイルが存在すれば真, 存在しなければ偽を返します.

■数字の1から9を表示する.
#!/bin/sh
number=1
while test $number -ne 10
do
    echo $number
    number=`expr $number + 1`
done

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

引用符
記号 意味
'...' 内部の文字列をそのままの文字列として返す.
"..." 内部に含まれる変数等 $,`...`,\ を解釈した結果の文字列を返す
`...` 内部にあるコマンド(群)を実行し, その標準出力を文字列として返す.

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


では>> 本日の課題
にチャレンジ!!

A 参考

A. 特殊記号のおさらい

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

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

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

代表的なメタキャラクタ
メタキャラクタ 意味 用例 用例の意味
* 任意の文字列を表す.
ls /dev/h*
ディレクトリ"/dev/"以下にある1文字目が"h"で始まるファイル全てを 表示せよ.
? 任意の1文字を表す. ?? は任意の2文字になる.
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を返す.

最終更新日: 2001/11/09(山田学) Copyright © 2000 inex