最低限 Unix / Linux [II] 【4. シェルスクリプト】

  1. 事前準備
  1. シェル
  1. テキストエディタ
  1. シェルスクリプト
  1. [付録] データ圧縮・解凍とアーカイブ
  1. 本日の課題
情報実験第 4 回トップへ

4 章は文章が多いです. がんばってください.

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

2 章で見たように, シェルはユーザと コンピュータの橋渡しをして, ユーザがコマンドを端末から打ち込む毎に, それを解釈し実行します.
しかしながら, ある一連の作業のために一つ一つのコマンドを個別に実行 していくのは大変な労力がかかります.
さらにそれが単純作業であったならなおさら徒労感があることでしょう.

それを解決してくれるのがシェルスクリプトです.
シェルスクリプトはその名のとおり「シェルの台本」であり, この台本を あらかじめ用意して必要に応じてシェルに読ませれば, 台本どおりにシェル が動いてくれるというテキストファイルです.

さらに, この台本には「制御構造」とよばれる作業手順を組み込むことも できます.
制御構造はいわゆる「プログラミング」の根幹を成す重要なしくみですので, それを学ぶ上でもシェルスクリプトは大きな意義があります.

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

[4-1] シェルスクリプトを作る(基本編)

具体的にどのようなものか, 簡単な例からトライしてみましょう. まずは次のような一連の仕事を行うスクリプトを作ることを考えます.

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

です(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 に読ませてみましょう.

$ bash sample.sh

とすると, 例えば次のような出力が得られます.

Fri May  7 04:12:57 JST 2010
I am chappy.
My login shell is /bin/bash
Hey chappy !!

この場合, bash をわざわざ起動して, その引数としてファイル名を指定し, そのファイルを bash が解釈して実行しています.
このままでも問題はないのですが, いちいち台本を読ませるシェルを指定せずに, シェルスクリプトファイルそのものをひとつのコマンドとして機能させることもできます.

そのためには, 以下の手順で先程のファイル(sample.sh)を実行可能なファイルにします.

  1. ファイル 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 !!" 
    

    [補足]: #! で始まる行を「shebang (シェバング)行」と呼びます. この行はスクリプトを解釈するコマンドの絶対パスを指定します.
    シェバング行では bash などのシェルに限らず, ruby, perl, php といった他のスクリプト言語を指定することも出来ます.

  2. ファイル sample.sh に実行パーミッションを加える.
    $ chmod 744 sample.sh
    

    もしくは

    $ chmod u+x sample.sh
    
  3. ls コマンドでパーミッションを確認.
  4. $ ls -l sample.sh
    -rwxr--r--    1 inex inex  204 Oct 24 05:12 sample.sh*
    
    
    <-- 所有者のパーミッションのところに, 実行権限である「x」がついていることを確認する.
    

こうすることでスクリプトのパスを入力するだけで 実行することができます.

$ ./sample.sh
<-- カレントディレクトリにシェルスクリプトがある場合はカレントディレクトリを表す ./ が必要となる. 

この場合, 現在使用中のシェルがスクリプトファイルを bash に解釈させ実行させていることになります.

[4-2] シェルスクリプトを作る(発展編)

基本編では, 単純に上から下に向かって読み進むだけのスクリプトを作成しましたが, 今度はもう少し複雑なスクリプトを作るための各種機能を試してみましょう.

[4-2-1] 引数処理

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

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

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

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

$ vi test.sh
#!/bin/bash
echo $1
echo $2

これを実行してみましょう. ただし実行時に引数として Hello, World! を渡してやります.

$ chmod u+x ./test.sh
$ ./test.sh Hello World!
Hello
World!
<-- 実行権限を与える
<-- 実行コマンドの後ろに引数をつける(今回は$1,$2を用いたので二つ付ける)
--> 引数1に渡した Hello を返す
--> 引数2に渡した World! を返す

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

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

[4-2-2] 制御構造

制御構造はあらゆるプログラミングの基本です. それには以下の 3 種類があります.

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

順序構造

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

選択構造

条件に応じて処理内容を区別する場合に用います. 選択構造には if , case を使います.

 if
書式
    if 条件式1
	then コマンド1
       [elif 条件式2
        then コマンド2]
              :
       [else
          コマンド3]
    fi
  
解説

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

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

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


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

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

繰り返し構造

ある条件の下で同じ処理を繰り返す場合に用います. 繰り返し構造には for , while , until を使います.

 for
書式
    for 格納する変数 in 格納したい値リスト
    do
      コマンド
    done
  
解説

for は一定回数の繰り返し処理または, ある値の集合に対して, それぞれの要素を処理する場合に使用します.

in 以降に指定された, 半角スペースで区切られた値リストの要素を in の前のシェル変数に設定し, そのたびに do と done の間に書かれたコマンド(列)を実行し続けます.

値リストとして ./* の様に「カレントディレクトリ内の全て」と指定するとカレントディレクトリ内のファイル名をそれぞれシェル変数に設定していってくれます.


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

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

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

処理の流れを制御する - 例

■与えられた引数の数を表示する.
#!/bin/bash
case $# in
    0) echo '引数なし' ;;
    1) echo '引数一つ' ;;
    2) echo '引数二つ' ;;
    *) echo '引数三つ以上' ;;
esac

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

■引数に指定された複数のファイル名に対し, 実在する物のみファイル名を表示する.
#!/bin/bash
while test $# -gt 0
do
    if test -f $1; then
        echo $1
    fi
    shift
done

※ test は, それに続く式を評価して値を返すコマンドです. 与えるオプションに応じて, 数値(整数)に関するテスト, ファイルの型に関するテスト, 文字列に関するテストを行うことが出来ます. 例えば, test -f [ファイル名]とすると, ファイルが存在すれば真, 存在しなければ偽を返します. test コマンドでテストできる数値は整数だけです.小数をテストしたい場合は,bc というコマンドがあるので,インターネット等で調べてみましょう.

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

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



シェルスクリプトの基本的な仕組みは無事習得できたでしょうか?
本日の課題はこのシェルスクリプトを用いた内容ですので, 必要に応じて復習しましょう.

以上で本日必修の実習内容は以上です.お疲れさまでした.
時間が余った方はこれまでのページにある付録や今回の課題で使う「ファイルの圧縮・解凍」, もしくは課題ページを読んでいてください.

作業終了後はログアウトをお忘れなく...


<< 戻る (テキストエディタ)      次へ >> (ファイルの圧縮・解凍),      >> 本日の課題

[4-3] 付録(シェルスクリプトで役立つ知識)

・引用符

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

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

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

$ var=ls

$ echo '$var'
$var

$ echo "$var"
ls

$ echo $($var)
sample.sh test.sh 
<-- シェル変数 var に ls を代入


--> '' 内の文字列をそのまま返す


--> "" 内の変数の値を返す


--> $() 内の変数をコマンドとして実行した結果を返す

より詳しい情報は man bash として bash のマニュアルを参照することで得ることも出来ます.

・特殊記号のおさらい

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

メタキャラクタ

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

代表的なメタキャラクタ
メタキャラクタ 意味 用例 用例の意味
* 任意の文字列を表す.
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"で始まるファイル全てを 表示せよ.

リダイレクションとパイプ

UNIX のコマンドは必ず標準入力と呼ばれる共通した入力の受け付け口と, 標準出力と呼ばれる共通した出力の生成, 標準エラー出力と呼ばれる共通したエラーメッセージの生成を行う機能が 備えられています. 複数のコマンドの標準入出力を繋ぎ合わせたり, ファイルへ/からの出力を行うために使う記号は リダイレクションとパイプ(パイプライン)と呼ばれます.
代表的な入出力リダイレクタ
記号 意味
| パイプと呼ばれる. program1|program2 とすると program1 の標準出力を program2 につなぐ.
> program >file とすると標準出力を file に切り換える.
>> >>file とすると標準出力を file に追加する.
2> program 2>file とすると標準エラー出力を file に切り換える.
< program <file とすると標準入力として file から読み込む.
<<string ヒア・ドキュメント. string と書かれた行が次に現れるまで, この後に続く行を標準入力にする.

その他

入出力リダイレクションとパイプ
記号 意味
; コマンドの区切り. 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 を実行する.

文字列演算子

基本的に文字列演算子構文では, 操作を指示する特殊な文字列を変数と右ブレース(})の間に挿入する. 演算子が引数を取る場合は演算子の右側に挿入する. 文字列操作演算子最初のグループは変数の存在を評価し, ある条件のもとでデフォルトの値を置換する.
文字列演算子
使い方 意味 目的
${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を返す.



<< 戻る (テキストエディタ)      次へ >> (データ圧縮・解凍)

最終更新日: 2020/06/01(茂木 遥平) 編集 Copyright © 2020 inex