UNIX スクリプト勉強会 資料

2001 (c) 葉山 薫

本文書は筆者:葉山薫が勤務先で実施した勉強会の資料から、 守秘義務に抵触すると思われる部分を修正し、一般向けに改めたものです。 なお、作成には私的時間を投じましたので、著作権は筆者にあります (勉強会自体は勤務時間でしたが)。 なお、記述の内容のために何らかの損害が生じましても筆者は一切関知しません。

本講座の目的・ねらい

参加者が、自分でシェルスクリプトを作成できるようになる。

基本的には、全てを勉強会で学ぶわけではなく、

というところに焦点を絞りたい。 本勉強会のねらいとしては

がある。

シェルとは

  1. UNIXの入力メニュー
  2. 言語インタープリタ

シェルスクリプトとは極端な話、キーボードから打つ一連のコマンドを 前もってファイル化しておいて、一括実行できるようにしたものと 考えればよい!これだけだと、DOS/Windowsのバッチファイルと同じだが、 UNIXのシェルスクリプトは豊富な制御構文をそろえており、 下手な言語に全く劣らない。

他の言語との違い

シェル

Ruby・Perl

Java

C言語

手早く書ける



時間がかかる
遅い



速い

Ruby・Perlの場合が手早く書ける場合もあります (というか、ほとんどそうですけど (^^;;

シェルスクリプトを書くメリット

デメリット

シェルの種類

名称 文法系列 編集機能 標準装備? コメント
sh
(BourneShell)
sh系 ゼロ 全UNIX標準装備 スクリプトが十分書けるし、どこでも通用する。 ぜひ、これでスクリプトを書くべき。 ただし、入力用シェルには向かない
csh csh系 ヒストリ・補完(貧弱) 全UNIX標準装備 もっぱら入力用。 C言語に文法が似ているので、csh というが、 スクリプト言語としては欠陥が多く、 推奨できない
bash sh系 完璧 Linuxでは標準装備 Open Source界での標準シェル。 GNU Project の産物の一つ。 Bourne Again SHell の略。 葉山のお気に入りシェル。
tcsh csh系 完璧 Linuxでは装備 csh に、bashなみの編集機能を追加したもの。 比較的愛用者が多い。
ksh sh系 それなり? 商用UNIXでは標準装備 商用UNIXベンダーが強化した sh。 編集機能が実はついているのだが、 ちょっとよく分からない。
cmd.exe
(おまけ)
cmd系 それなり? NT,2000,OS/2 command.com を強化・改良したシェル。 それでも使えるのは簡単なバッチのみ。 (OS/2ではREXX言語を内蔵している)

sh と csh

csh は C言語に似た文法を持つので、csh という名前を持っている。 が、幾つかの欠陥を持ち、スクリプト言語として使用することは 推奨しない。もっぱら入力シェルとしてのみ使われるべきである。 (欠陥として知られているものには、 標準出力・標準エラー出力を独立してリダイレクトできない、 単引用符('〜')内の文字を変に解釈する等がある)

一方、sh は最も古いシェルであるが、プログラミング言語として 十分な機能を持っており、全UNIXで装備しているため、 身につけておくと、とても幸せである。 ただ、残念ながら、編集機能がほとんどないため、 入力用シェルとしては使えない。

以上の経緯から

を使うのが一般的になっている。 そのため、結果的に両方学ばなくてはいけないという不幸な状態になっている。 Open Source界では、両方 bash 使って幸せになっていることが多い。

注意

以下では、特に明示の無い限り、文法は sh のものを指す。 とはいえ、入力用シェルとして csh , tcsh はよく使用するので、 それについて述べる際は、(sh),(csh)と区別する。

余談:その他のスクリプト言語

いわゆる、スクリプト言語には次のようなものがある。

awk(オーク)

スクリプト言語の元祖ともいえる存在。 あらゆる UNIX に標準装備されていることが強み。 このことは、自分が root 権限を持っていない環境においても 使用できることができることを示す。 基本的にフィルタ言語であり、 標準入力から得られた内容を加工して、結果を標準出力に出すという 用途を得意とするが、その範疇を越えると、結構、面倒になる。 文法が C 言語に似ており(作者の一人が C 言語の作者のカーニハン だから当然?)、習得しやすい。 シェルスクリプトとの連携もしやすい。

Perl(パール)

スクリプト言語の本家。Larry Wall という人が、 csh・awk・C言語などの言語からよいところを抽出して作った。 それだけに、かなり強力で、極端な話、 サーバーサイドで必要とされることは たいてい何でもできてしまう(ようだ)。

それまでの言語が、小さなツールを組み合わせて何かをするという 視点の元で作られていた(ToolBox Approch)のに対し、 Perl はこれ一つで多くのことをこなせるようになっている (KitchenSink Approach)。

手軽にプログラムを作れる反面、それを実現するために、 不可解(と初心者には思われる)な約束事が多く、 プログラムが長くなればなるほど、読みにくくなるという 弱点があり、アンチPerl派の攻撃の的になっている。

Linux , *BSD 以外は標準装備というわけでもないので、 自分でインストールしなくてはいけない。 が、最近は有名になったので、結構、黙っていてもインストールされている ことが多いようだ。

Ruby(ルビー)

国産のオブジェクト指向スクリプト言語。 まつもとゆきひろ氏が開発した。 葉山が今一押しの言語。 開発当初より、オブジェクト指向を意識しており、 プログラムが少々大きくなっても、美しく記述できる。 それでいて、Perl 以上に必要な機能を簡潔に記述できるなど、 今のところ文句のつけどころがない。

問題は、最近人気急上昇とはいえ、まだ、それほどメジャーではないところ。

    #!/usr/local/bin/ruby

    IO.popen("ps -ef","r") do |pp|
        pp.each_line do |line|
            kill line.split(/\s/)[1] if line =~ /java/ ;
        end
    end

最も簡単な(シェル)スクリプトの作り方

例えば、こんなしょうもないシェルスクリプトを実行できるようにするには

#!/bin/sh

echo 'Hello, shell world!'
  1. エディタでテキストファイルにスクリプトを書く(必須)
  2. 先頭行に「#!インタプリタのフルパス名」 という1行を入れる
  3. ファイルに実行属性をつける (chmod +x ファイル名)
  4. 環境変数 PATH の通ったディレクトリへ置く

1番目以外は必須というわけではない。

2番目+3番目は、スクリプトを「スクリプト名↓」だけで実行する為の 処置である。「インタプリタ名 スクリプト名↓」で起動するのならば 行う必要はない。

4番目を行わないと、スクリプトを実行する際、そのスクリプトの フルパス(あるいはカレントディレクトリからの相対パス)を 指定しなければいけない。PATH の通ったディレクトリにおけば、 スクリプトのあるディレクトリを毎回指定する必要はなくなる。

PATHを通すってなんじゃ

環境変数PATHにディレクトリを登録しておくと、 そのディレクトリに置いた実行属性のあるコマンドは ディレクトリ名を記述せずとも、ファイル名だけを指定すれば 実行できる。

つまり、PATH が空だと、/bin ディレクトリにある vi を起動するには 「/bin/vi」とタイプしなければいけないが、 PATHの内容が「/bin:/usr/bin:/usr/ccs/bin/:/usr/local/bin」などと 定義されている場合、「vi」だけでよくなる。 環境変数の定義の仕方は次の通り。

入力シェルがsh系

PATH=/bin:$PATH
export PATH

これを ~/.profile や ~/.bashrc に記述しておいて、 ログインし直すか、「source ~/.profile」 「. $HOME/.profile」などを実行する。

入力シェルがcsh系

setenv PATH /bin:$PATH
    あるいは
set path=(/bin $path)

これを ~/.cshrc に記述しておいて、ログインし直すか、 「source ~/.cshrc」を実行する。

細かい文法については後述する。

実行属性が消える?

vi とか Mule など、UNIXのエディターを使っている分には問題ないが、 Samba や ftp を経由して、Windowsのエディターでスクリプトを編集 すると実行属性が消えてしまう。 これは、Windowsのエディターがファイルを上書き保存する際に、 旧ファイルを別名に改名した後、新ファイルとして毎回新規作成してしまう ためである。 Mule も同様のことをするが、属性をちゃんと考慮しているため、 このようなことはない。

/bin/sh の基本文法の解説

変数

両方とも「$変数名」で参照する。 シェル変数は「変数名=内容」で内容を設定する。 環境変数は「export シェル変数名」とすれば、そのシェル変数の内容を 同じ名称の環境変数として子プロセスへ公開する。

HOGEHOGE=hogehoge
echo $HOGEHOGE

特殊変数

制御構文

if コマンド1
then
    # コマンド1が正常終了した時に
    # 実行するコード
else
    # コマンド1がエラー終了した時に
    # 実行するコード
fi
while コマンド2
do
    # コマンド2が正常終了している間、
    # 実行するコード
done
for シェル変数名 in 値1 値2 値3…
do
    # 値1,値2,値3に関して実行するコード
done
case 文字列 in
パターン1)
    # 文字列がパターン1に合致するときに
    # 実行されるコード
    ;;
パターン2|パターン3)
    # 文字列がパターン2、あるいは3に
    # 合致するときに実行されるコード
    ;;
esac
# パターンとしてはワイルドカードの特殊文字が使える
コマンド1 && 《コマンド1が正常終了した時に実行するコマンド》

コマンド1 || 《コマンド1がエラー終了したときに実行するコマンド》

コマンド1 ; 《コマンド1が終了した後に実行するコマンド》

コマンド1 & 《コマンド1と同時に実行するコマンド》
関数名(){
    # 関数の実行コード
    # 引数は $1,$2などで参照できる。
    # 定義した関数は普通の外部コマンドのように使うことができる。
    return エラーコード
}
# 注意:関数名と丸括弧の間に空白を入れてはいけない。

test コマンド

比較を行うコマンド。パラメータの条件が成立していたら、 正常終了し、成立していなかったら、エラー終了する。

    test $VAR = 'aho'
    test $VAR != 'aho'
    test -r $HOME/.cshrc
    [ $VAR = 'aho' ]

[〜] は、test コマンドのエイリアス(別名)である

    if [ "$1" = "-e" ]; then
        echo オプションとして -e が指定されました。
        echo 指定されても何もせんけど。
    fi

引用符

引用符は、特殊文字や空白を含んだ文字列を囲んでひとまとまりとして 扱うためのものである。

「’」シングルクォート(Shift+7)
内部に記述された変数を展開しない。 記号を解釈しない(「\',\\」は別)。 ワイルドカードを展開しない。 この中に他の言語をそっくり書くということも結構ある。
「”」ダブルクォート(Shift+2)
中に「$変数名」が入っていると、その部分は、変数の中身と置換される。 特殊記号などをシェルとして解釈される。 ワイルドカードを展開しない。
「‘」逆クォート(Shift+@)
中の文字列を命令だと解釈して、その実行結果(標準出力の内容)で置換される。 とっても便利!

例:ファイルを日付をつけてバックアップする(savedate.sh)

    #!/bin/sh
    DATE=`date '+%y%m%d'`

    for i in $*
    do
        NEWNAME=$i-$DATE
        cp $i $NEWNAME
    done
(例)
    2001年5月12日に
        savedate.sh BaseServlet.java BidCategory1.java
    を実行すると、
        BaseServlet.java
        BidCategory1.java
    の複製として
        BaseServlet.java-010512
        BidCategory1.java-010512
    というファイルを作成する。

$* は、savedate.sh に与える引数。date は現在の日時を与えられた フォーマットに従って出力するコマンド。 NEWNAME 行にある「-」は引き算ではなく、ただのハイフン。

case "`hostname`" in
nureyev)
    echo 'このサーバーはSYS2のです'
    ;;
karl)
    echo 'このサーバーは大M1共用です'
    ;;
*)
    echo 'なんでしょ、これ?'
    ;;
esac

hostname は、サーバーのホスト名を出力するコマンド

リダイレクト

UNIX プログラムには、基本的に次の入出力先が用意されている。

標準入力(stdin)
通常はキーボード(厳密には違うが)に接続されている。
標準出力(stdout)
通常は画面(teraterm?)に接続されている。
標準エラー出力(stderr)
通常は画面(teraterm?)に接続されている。 エラー用に別途設けられている

これら標準入出力は、簡単に切り替えることが出来る。 コマンド実行時に次の節を加えればよい。

コマンド < ファイル名
[標準入力の切替] 通常キーボードから入力する内容を変わりにファイルの内容を流し込む
コマンド > ファイル名
コマンド 1> ファイル名
[標準出力の切替] 通常画面に出力される内容を(画面に出さず)ファイルに書きこむ
コマンド 2> ファイル名
[標準エラー出力の切替] 画面に出力されるエラー内容を(画面に出さず)ファイルに書きこむ
2>&1
標準エラー出力の先を標準出力と同じにする。
1>&2
標準出力の先を標準エラー出力と同じにする。

「> ファイル名」普通に使うと、そのファイル名のファイルを新規作成する。 これを既存のファイルの末尾に追記書きこみするようにするには「>>」 とする(2> も同じ)

csh では「2>…」はなく、 「1>」と「2>」を合成した「>&」が存在する。

あるコマンドの標準出力と別のコマンドの標準入力を接続することもできる。 これをパイプと呼ぶ

    ps -ef | grep 'java'

ps は現在稼動中のプロセス一覧を標準出力へ吐き出すコマンド。 grep は標準入力(あるいは第二引数のファイル)の内容から、 第一引数のパターン(正規表現)に合致する行を標準出力へ出すコマンド。

これ、すなわち、java関連のプロセス一覧を出す機能になる

注意1

「-ef」 は Solaris/HPUX の /usr/bin/ps 用オプションである。 Linux や BSD では「-auwx」を使う。

注意2

「ps -ef | grep 'java'」では、grep 自身がひっかかる可能性がある。

コマンドの使い方の調べ方

manコマンドを利用する

基本的に「man コマンド名」で Ok !

これを実行しても、分からない場合、 環境変数 MANPATH に、マニュアルページのディレクトリが 指定されていない可能性がある。

「echo $MANPATH」を実行して内容を確認する。 もし、空だったりしたら、 マニュアルディレクトリ(manという名前)を探し、

csh系シェルの場合

    setenv MANPATH /usr/man:$MANPATH

を .cshrc に追加する。

sh系シェルの場合

    MANPATH=/usr/man:$MANPATH
    export MANPATH

を .bashrc か .profile に追加する。

マニュアルディレクトリを探せ(1)

たいていの場合、自分の環境(ユーザ)だけが設定されていなかったりする。 他のユーザの .bashrc , .profile などを覗いてみよう。 なお、ユーザ名とそのホームディレクトリの一覧は /etc/passwd を参照する。

マニュアルディレクトリを探せ(2)

マニュアルディレクトリは、たいてい /usr/man とか /usr/local/man とか man という名前であり、その下に man1 とか man2 とかいう サブディレクトリがあれば大正解である。それを探してみよう。

もし、すぐに分からない場合、力技として

    find / -name "man" -print

で探すこともできる。だが、この技、サーバーにかなりの負荷を与えるので、 稼動中の本番サーバーでやるのはやめましょう。

日本語で出ない場合

「setenv LANG ja」(csh系) あるいは「LANG=ja ; export LANG」(sh系) を試してみましょう。

日本語訳がタコな場合

たまに使い物にならない場合があります。 「setenv LANG C」(csh系) あるいは「LANG=C ; export C」(sh系) を試してみましょう。

ウェブに頼る

べたべたなことではあるが、ウェブページも有効な手段だ。 例えば find というコマンドの使い方を調べる場合:

  1. google 等検索エンジンを使う
  2. www.linux.or.jp とか、UNIX系のポータルサイトをのぞく (linuxのサイトだが、UNIX共通の話もある)

誰かに尋ねる

誰か知ってるでしょ(おい)

次回予告:多く(awk)は書かねぇ、たった1行。

awk言語の処理系

awk 初版 最初のawk。入門書に記述されるサンプルも実行できないことが多い。 Linux では gawk にハードリンクされている。
gawk GNU AWK GNU の awk。Linux/FreeBSD では標準装備。
nawk New AWK 商用UNIXに標準装備された 新awk。 一応、gawk 並の機能は持っている(はず)。

awk とはパターンマッチ言語と言われ、 標準入力orファイルの内容を1行ずつ走査し、 パターンにマッチする行に対して、起こすべきアクションを記述する形で プログラムを記述する言語である。

    パターン {
        アクション
    }

パターン部分には grep でお馴染みの正規表現や 普通の比較演算を記述する。 アクション部分にはCに似た言語を記述できる。 (awk の作者の一人はC言語の作者でもある)

プログラムは通常 「awk -f プログラムファイル名」でファイルから読み込む。 無論、「#!/bin/awk -f」と1行目に記述して、 実行属性を立てれば、「プログラムファイル名」だけで実行できる。

だが、次のようにシェルスクリプトの中にインラインで記述することもできる!

例:java関係のプロセスを皆殺しにする

    for i in `ps -ef | nawk '/java/{ print $1 }'`
    do
        kill -9 $i
    done

実は、これを実行すると、nawk が自分自身のプロセスIDまで出力してしまう。 これを防止する方法として、 「/java/」を「/[j]ava/」とすればよい。 なぜ、これでいいか考えてみよう。

例:WebLogicのプロパティーファイルにサーブレットを自動登録する

cd /weblogic
ls $HOME/app_root/servlet | nawk '
    /\.class$/{
        classname = substr($0,1,length($0)-6)
        printf "weblogic.….%s=jp.co.hogehoge.%s\n",
            classname , classname
    }
' | cat - weblogic.properties.base > weblogic.properties

太字部分は awk 言語(Solarisなので nawk を使っている)。 元の weblogic.properties を weblogic.properties.base にリネームし、 本スクリプトを実行する。

Appendix

多くのコマンドで使える正規表現

特殊文字マッチ対象
^ 先頭にマッチする
$ 末尾にマッチする
. 任意の一文字にマッチする
* 直前の文字の 0 個以上の繰り返し
[〜]〜内の文字のいずれかにマッチする
[^〜]〜内にない文字にマッチする
\次の1文字の機能を打ち消す

ワイルドカード(シェルで使える簡易正規表現)

特殊文字マッチ対象
* 0文字以上の任意の文字列にマッチする
? 任意の1文字にマッチする
[〜]〜内の文字のいずれかにマッチする
[^〜]〜内にない文字にマッチする
\次の1文字の機能を打ち消す

プロセス監視・自動復旧スクリプト

hogehogeバージョンです。 UNIX には init という、特定のプロセスを監視・自動起動させるシステムがあります。 が、これは「被監視プロセス」=「再起動プロセス」でないとなければなりません。 そのため、WebLogic のような起動したコマンドが別のプロセスを立ち上げるような 場合ではそのまま使えません。

ということで、init と WebLogic の間に、監視するためのプロセスを 一つかませることで、本問題を解決します。

#!/bin/sh

# 本プログラムは root ユーザで実行しないと動作しない.
# 通常は コマンドラインから実行するのではなく、/etc/inittab に
#	ag:3:respawn:/bin/sh /usr/hogehoge/weblogic/hogehogeguard
# という行を追加して、
#	/sbin/init Q
# を実行する。
# (3 という数字は場合によっては 4 5 6 のこともある)

# ログの出力先
LOGFN=/usr/hogehoge/app_root/log/lifewatch.log

# ログ出力関数定義
printlog(){
    echo "★ `date '+%y/%m/%d %H:%M'` :: $*" >> $LOGFN
}

cd /usr/hogehoge
printlog "inittab が hogehogeguard を再起動しました."

while true
do
    # WebLogic が死んでいたら、再起動する.
    if ps -ef | grep "java.*-DWL[1]" >/dev/null ; then
	:
    else
	printlog "hogehogeguard が WebLogic を再起動しました."
	su - hogehoge -c 'csh -c "source .cshrc ; 
            ksh /usr/hogehoge/weblogic/runwl" ' > /dev/null
    fi

    # バッチ監視プロセスが死んでいたら、再起動する.
    if ps -ef | grep "java.*-DBT[1]" >/dev/null ; then
	:
    else
	printlog "hogehogeguard が バッチ監視プロセスを再起動しました."
	su - hogehoge -c 'csh -c "source .cshrc ; 
            ksh /usr/hogehoge/weblogic/runbats" ' > /dev/null
    fi
    sleep 15
done

su [-] ユーザ名 [-c コマンド]
Super User / Switch User の略。別のユーザに化けるコマンド。 実行ユーザが root 以外の時は、パスワードの入力を促される (上記の hogehogeguard コマンドは、root での実行を前提とする)。
/dev/null
デバイスファイルの一つ。ファイルの体裁をしているが、その実態は デバイスのインターフェイス。 UNIX はたいていの操作対象はファイルの形で表現されている。 /dev/null の場合、ここに書きこんだ値は全て捨てられる。