2001 (c) 葉山 薫
| 本文書は筆者:葉山薫が勤務先で実施した勉強会の資料から、 守秘義務に抵触すると思われる部分を修正し、一般向けに改めたものです。 なお、作成には私的時間を投じましたので、著作権は筆者にあります (勉強会自体は勤務時間でしたが)。 なお、記述の内容のために何らかの損害が生じましても筆者は一切関知しません。 |
参加者が、自分でシェルスクリプトを作成できるようになる。
基本的には、全てを勉強会で学ぶわけではなく、
というところに焦点を絞りたい。 本勉強会のねらいとしては
がある。
シェルスクリプトとは極端な話、キーボードから打つ一連のコマンドを 前もってファイル化しておいて、一括実行できるようにしたものと 考えればよい!これだけだと、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言語を内蔵している) |
csh は C言語に似た文法を持つので、csh という名前を持っている。 が、幾つかの欠陥を持ち、スクリプト言語として使用することは 推奨しない。もっぱら入力シェルとしてのみ使われるべきである。 (欠陥として知られているものには、 標準出力・標準エラー出力を独立してリダイレクトできない、 単引用符('〜')内の文字を変に解釈する等がある)
一方、sh は最も古いシェルであるが、プログラミング言語として 十分な機能を持っており、全UNIXで装備しているため、 身につけておくと、とても幸せである。 ただ、残念ながら、編集機能がほとんどないため、 入力用シェルとしては使えない。
以上の経緯から
を使うのが一般的になっている。 そのため、結果的に両方学ばなくてはいけないという不幸な状態になっている。 Open Source界では、両方 bash 使って幸せになっていることが多い。
以下では、特に明示の無い限り、文法は sh のものを指す。 とはいえ、入力用シェルとして csh , tcsh はよく使用するので、 それについて述べる際は、(sh),(csh)と区別する。
いわゆる、スクリプト言語には次のようなものがある。
スクリプト言語の元祖ともいえる存在。 あらゆる UNIX に標準装備されていることが強み。 このことは、自分が root 権限を持っていない環境においても 使用できることができることを示す。 基本的にフィルタ言語であり、 標準入力から得られた内容を加工して、結果を標準出力に出すという 用途を得意とするが、その範疇を越えると、結構、面倒になる。 文法が C 言語に似ており(作者の一人が C 言語の作者のカーニハン だから当然?)、習得しやすい。 シェルスクリプトとの連携もしやすい。
スクリプト言語の本家。Larry Wall という人が、 csh・awk・C言語などの言語からよいところを抽出して作った。 それだけに、かなり強力で、極端な話、 サーバーサイドで必要とされることは たいてい何でもできてしまう(ようだ)。
それまでの言語が、小さなツールを組み合わせて何かをするという 視点の元で作られていた(ToolBox Approch)のに対し、 Perl はこれ一つで多くのことをこなせるようになっている (KitchenSink Approach)。
手軽にプログラムを作れる反面、それを実現するために、 不可解(と初心者には思われる)な約束事が多く、 プログラムが長くなればなるほど、読みにくくなるという 弱点があり、アンチPerl派の攻撃の的になっている。
Linux , *BSD 以外は標準装備というわけでもないので、 自分でインストールしなくてはいけない。 が、最近は有名になったので、結構、黙っていてもインストールされている ことが多いようだ。
国産のオブジェクト指向スクリプト言語。 まつもとゆきひろ氏が開発した。 葉山が今一押しの言語。 開発当初より、オブジェクト指向を意識しており、 プログラムが少々大きくなっても、美しく記述できる。 それでいて、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番目+3番目は、スクリプトを「スクリプト名↓」だけで実行する為の 処置である。「インタプリタ名 スクリプト名↓」で起動するのならば 行う必要はない。
4番目を行わないと、スクリプトを実行する際、そのスクリプトの フルパス(あるいはカレントディレクトリからの相対パス)を 指定しなければいけない。PATH の通ったディレクトリにおけば、 スクリプトのあるディレクトリを毎回指定する必要はなくなる。
環境変数PATHにディレクトリを登録しておくと、 そのディレクトリに置いた実行属性のあるコマンドは ディレクトリ名を記述せずとも、ファイル名だけを指定すれば 実行できる。
つまり、PATH が空だと、/bin ディレクトリにある vi を起動するには 「/bin/vi」とタイプしなければいけないが、 PATHの内容が「/bin:/usr/bin:/usr/ccs/bin/:/usr/local/bin」などと 定義されている場合、「vi」だけでよくなる。 環境変数の定義の仕方は次の通り。
PATH=/bin:$PATH export PATH
これを ~/.profile や ~/.bashrc に記述しておいて、
ログインし直すか、「source ~/.profile」
「. $HOME/.profile」などを実行する。
setenv PATH /bin:$PATH
あるいは
set path=(/bin $path)
これを ~/.cshrc に記述しておいて、ログインし直すか、 「source ~/.cshrc」を実行する。
細かい文法については後述する。
vi とか Mule など、UNIXのエディターを使っている分には問題ないが、 Samba や ftp を経由して、Windowsのエディターでスクリプトを編集 すると実行属性が消えてしまう。 これは、Windowsのエディターがファイルを上書き保存する際に、 旧ファイルを別名に改名した後、新ファイルとして毎回新規作成してしまう ためである。 Mule も同様のことをするが、属性をちゃんと考慮しているため、 このようなことはない。
両方とも「$変数名」で参照する。 シェル変数は「変数名=内容」で内容を設定する。 環境変数は「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 $VAR = 'aho'
test $VAR != 'aho'
test -r $HOME/.cshrc
[ $VAR = 'aho' ]
[〜] は、test コマンドのエイリアス(別名)である
if [ "$1" = "-e" ]; then
echo オプションとして -e が指定されました。
echo 指定されても何もせんけど。
fi
引用符は、特殊文字や空白を含んだ文字列を囲んでひとまとまりとして 扱うためのものである。
#!/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 プログラムには、基本的に次の入出力先が用意されている。
これら標準入出力は、簡単に切り替えることが出来る。 コマンド実行時に次の節を加えればよい。
「> ファイル名」普通に使うと、そのファイル名のファイルを新規作成する。 これを既存のファイルの末尾に追記書きこみするようにするには「>>」 とする(2> も同じ)
csh では「2>…」はなく、 「1>」と「2>」を合成した「>&」が存在する。
あるコマンドの標準出力と別のコマンドの標準入力を接続することもできる。 これをパイプと呼ぶ
ps -ef | grep 'java'
ps は現在稼動中のプロセス一覧を標準出力へ吐き出すコマンド。 grep は標準入力(あるいは第二引数のファイル)の内容から、 第一引数のパターン(正規表現)に合致する行を標準出力へ出すコマンド。
これ、すなわち、java関連のプロセス一覧を出す機能になる
「-ef」 は Solaris/HPUX の /usr/bin/ps 用オプションである。 Linux や BSD では「-auwx」を使う。
「ps -ef | grep 'java'」では、grep 自身がひっかかる可能性がある。
基本的に「man コマンド名」で Ok !
これを実行しても、分からない場合、 環境変数 MANPATH に、マニュアルページのディレクトリが 指定されていない可能性がある。
「echo $MANPATH」を実行して内容を確認する。 もし、空だったりしたら、 マニュアルディレクトリ(manという名前)を探し、
setenv MANPATH /usr/man:$MANPATH
を .cshrc に追加する。
MANPATH=/usr/man:$MANPATH
export MANPATH
を .bashrc か .profile に追加する。
たいていの場合、自分の環境(ユーザ)だけが設定されていなかったりする。 他のユーザの .bashrc , .profile などを覗いてみよう。 なお、ユーザ名とそのホームディレクトリの一覧は /etc/passwd を参照する。
マニュアルディレクトリは、たいてい /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 というコマンドの使い方を調べる場合:
誰か知ってるでしょ(おい)
| 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行目に記述して、 実行属性を立てれば、「プログラムファイル名」だけで実行できる。
だが、次のようにシェルスクリプトの中にインラインで記述することもできる!
for i in `ps -ef | nawk '/java/{ print $1 }'`
do
kill -9 $i
done
実は、これを実行すると、nawk が自分自身のプロセスIDまで出力してしまう。 これを防止する方法として、 「/java/」を「/[j]ava/」とすればよい。 なぜ、これでいいか考えてみよう。
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 にリネームし、 本スクリプトを実行する。
| 特殊文字 | マッチ対象 |
|---|---|
| ^ | 先頭にマッチする |
| $ | 末尾にマッチする |
| . | 任意の一文字にマッチする |
| * | 直前の文字の 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