初出:SoftwareDesign 2003/9月号
第2特集 UNIXプログラミングツールをマスターせよ
◆シェルスクリプト実践テクニック〜bash編
					くわむら じゅん
					juk@yokohama.email.ne.jp 


・bashシェルスクリプトの特徴,利点

「プログラミングはスクリプトに始まりスクリプトに終
る」とは誰か言ってそう(?)ですが、最近はJAVAがコン
パイラ言語のほうの人気をうんと高めてくれてるようで
す。しかしながら、JAVA というのがこれまでのプログ
ラミング言語の範疇にあてはまるのかどうかは、JAVAの
無い時代のコンパイラ言語でプログラムを書いてきた者
としては疑問になります。少なくとも筆者の知っている
プログラミング言語はいくつかの文の使いまわしを覚え
るとプログラムを書くことができたはずなのですが、こ
と、JAVA になるとさっぱり手が付けられせん。JAVAで
アプリケーションを作っている人に聞くと、プログラム
を書くというよりも、必要な機能をインターネットで拾っ
てカット&ペーストということらしく、あたかも、パズ
ルを組み立ているかのようです。まさに工場で言うとこ
ろのアッセンブリ工程のようなものだと思いました。こ
のあたりが、単なる言語を越えたものである結縁なので
しょう。これほどまでに信者を集めたJAVAにはほとんど
のものが揃っているということでもあると思います。

JAVAのために用意されたJCSPというクラスは、コンパク
トな通信カーネルで、スレッドなど使わずに安全な並列
分散処理ができてしまうそうです。通信をするためのソ
ケットプログラミングを例にとると、C言語では少なく
とも6、7ステップ(ソケットを決めて、初期化して、
プロトコルを決めて、ポートを決めて、バインドして、
データをやりとりする)かかってしまう通信のためのソ
ケットプログラミングも、JAVAで書くと2、3ステップ
(ソケットを作成し、入力ストリーム決め、出力ストリー
ム決め、データをやりとりする)で済んでしまいます。
ところが、JCSPではソケットさえ意識しないで通信がで
きてしまうというので驚きです。とはいえ、ネットワー
クがコンピューティングの基本要素となった昨今では、
とてもありえる話です。

JCSP=> http://members.jcom.home.ne.jp/1355/CSP_Java.htm

シェルプログラミングでもそういう通信機能が使えれば
便利なのになあと思うこともあります。確かに、TCLや
PERLなどのスクリプト系プログラミング言語ではそうし
た機能拡張も盛んに行なわれていますが、bashを含めた
シェル系では言語の拡張機能にして使うよりも、既に作
られたプログラムを利用することのほうが多く、複数の
プログラムを組み合わせ実行してバッチ処理を行なう、
シェルプログラミングのほうが主体といえましょう。プ
ログラミングにシェルを選択するかどうかはこのあたり
の見極めが大事です。もちろん、bashにおいても拡張機
能の追加は可能で、PgBashのようにシェルから直接に
PostgreSQL DBMSを操作するような便利な使い方もあり
ます。

PgBash=> http://www.psn.co.jp/PostgreSQL/pgbash/index-j.html

ちょっとしたファイルの整形やテキスト処理を複数のファ
イルに対して行なう必要がある場合はどうでしょうか?
日常的な業務においては単純な処理の繰返しはよくあり
ます。異なる文字コードを使っているコンピュータ間で
のファイルのやりとり、扱うデータの書式が少し異なる
アプリケーション間でのデータやりとり。ファイルをま
とめて印刷したり、ログの整理をしたり。手で行なって
も簡単にできるけど、同じ作業の繰返しをするとなると、
おっくうだし、単純作業を続けるとミスをしがちになり
ます。そこで、複数の操作をひとつのファイルにして間
違えのないように、作業を省力化して効率をあげたいと
きにシェルプログラミングを覚えていれば、とても役に
たちます。

C言語を使った経験のある人ならCシェルを使うほうが比
較的簡単ですが、OSまわりのシェルスクリプトはBシェル
(Bournシェル)系のほうが多いようです(かつては、
CONVEX のように Perlを使っているOSもありました)。
LinuxではBシェル(sh)の代わりにbashが使われてい
ます。bashは sh との互換性を維持しながら拡張されて
いますので、シグナルをトラップできることはいうまで
もなく、変数の照合演算子や数値演算など、プログラミ
ング言語としても便利な機能が多く追加されています。
それでは、bashでプログラミングをするための機能を
見てみましょう。


・条件分岐,変数,引数などの解説

============
変数について
============

bashの変数はダラー記号($)で始まる変数名のついた文
字列です。変数の定義はダラー記号が付かない変数名に
続けて等号記号(=)、それに続けて値を記述します。変
数名の先頭にダラー記号を付けて参照することができま
す。正確には、${変数名} のように変数名を左右のカー
リーブレースで囲むのですが、左右のブレース記号は省
略可能です。次の例は varname という名前の変数に 
"newvalue" という文字列を与え、それを表示していま
す(行頭の'$ 'はbashのコマンドラインプロンプトを示
します)。


	--
	$ varname=newvalue
	$ echo $varname
	newvalue
	--

一般的に大文字で定義される環境変数は export するこ
とによってサブプロセスから参照することができます。
bash ではexport文で変数への代入も同時にできるよう
になっています。

	--
	$ export LNAG ja_JP.eucJP
	--

言い方を代えると、現行シェルでexportされた変数は、
そのシェルから起動されたプログラム(サブプロセス)
からも参照可能になります。

  プログラムを実行する際にスペースやタブ記号で区切
られた引数として様々なオプションをわたすことがある
と思います。bashのサブプロセスはこれら引数を位置パ
ラメータの変数として扱うことができます。引数の位置
パラメータは指定された順番に、左から順番に1,2,3... 
と数字の名前が付けられ、bashスクリプトの中では 
$1, $2, $3...のように変数として参照が可能です。な
お、$0 は起動された時に指定された自分自信のパス名
となります。$# は引数の個数になります。また、$* と 
$@ は引数すべての文字列となります。(いずれも、0番
目の位置パラメータの値は含まれません)。IFS変数が指
定された場合は、これらの変数は指定された区切り文字
で分割された、複数の引数として内部的には扱われます。
例えば、次のようなスクリプトのファイルをつくり実行
してみると、

test1.sh
--
IFS=,
echo "$# arguments"
function countarguments
{
  echo "$# arg."
}
countarguments $*
--

	--
	$ . test1.sh a1 a2 a3
	3 arg.
	3 arg.
	$ test1.sh a1,a2,a3
	1 arg.
	3 arg.
	--

カンマ区切りの文字列を渡した場合も、関数内部では3
つの引数として扱われています。


============
関数について
============

関数がでてきたのでついでにその説明をしておきます。
bash の関数はつぎの2種類の方法で定義することが
できます。

その1)
	function 関数名
	{
		関数の処理内容
	}

その2)
	関数名 ()
	{
		関数の処理内容
	}

いずれも呼び出された関数内での引数はシェルと同様の 
$1, $2, $3...のような位置パラメータ変数として参照
可能です。これらの位置パラメータ変数は、$0を除くと、
関数を呼び出すのものとは異なりますので注意してくだ
さい。たとえば、次のようなスクリプトをつくり、

test2.sh
--
function myfunc
{
  echo "func=>\$0:$0, \$1:$1, \$2:$2, \$3:$3."
}
myfunc
myfunc a b c
echo "main=>\$0:$0, \$1:$1, \$2:$2, \$3:$3."
--

引数を指定して実行してみると、

	--
	$ . test2.sh 1 2 3
	func=>$0:test2.sh, $1:, $2:, $3:.
	func=>$0:test2.sh, $1:a, $2:b, $3:c.
	main=>$0:test2.sh, $1:1, $2:2, $3:3.
	--	

メインのシェルと関数の中では位置パラメータ変数は関
係のないことがわかります。

では、他の変数はどうでしょうか。例えば、次のような
スクリプトをつくり、

test3.sh
--
function myfunc1
{
  var=$(($var+1))
  echo "func=>$var"
}
var=1
echo "main=>$var"
myfunc1
echo "main=>$var"
--

実行してみると、

	--
	$ . test3.sh
	main=>1
	func=>2
	main=>2
	--

という具合に、変数は引数にしなくても関数に渡り、関
数内で変更された変数値は関数を呼び出したメインのシェ
ルに戻っても残っています。


========
数値演算
========

bashは次の表に示す数値演算子による整数演算を行なう
ことができます。

  演算子	演算
------------+----------------------------
    +		加算
    -		減算
    *		乗算
    /		除算(小数点以下切捨て)
    %		剰余
    <<		左へビットシフト
    >>		右へビットシフト
    &		論理積
    |		論理和
    ~		論理否定
    !		論理否定
    ^		排他的論理和

先ほどの test3.sh では var=$(($var+1)) という代入
文がでてきました。この行は、 $var+1 を数値として計
算してvarへ代入をしています。このように $(( と )) 
とで囲まれたブロックは数式として処理されます。この
数値演算式では変数に $ を付ける必要はありません。
bash の数値演算処理で注意しなくてはならないことは、
整数を処理対象としていることです。例えば、次のよう
なスクリプトをつくり、

test4.sh
--
function myfunc2
{
  val=$1
  var=$((val+1))  
  echo "func=>$var"
}
myfunc2 3
myfunc2 3.3
--

実行すると小数の場合はエラーになります。
	--
	$ . test4.sh
	func=>4
	bash: 3.3: syntax error in expression (error token is ".3")
	--

数値演算では上記の数値演算子の他に以下の関係演算子
(比較演算子と論理演算子)による演算もできます。

  演算子	意味
------------+---------------
    <		より小さい
    >		より大きい
    <=		以下
    >=		以上
    ==		等しい
    !=		等しくない
    &&		かつ
    ||		または


bashによる数値演算は、このほかに let 文を使うこと
もできます。 let 文で使う変数を declare 文で整数値
として宣言しておく必要があります。例えば、次のスク
リプトのように記述できます。

test5.sh
--
declare -i a=1 b=2
let c=$a+$b
echo "$a + $b = $c"
--

もし代入される変数も declare で整数に宣言しておけ
ば、この let は省略することも可能です。例えば、次
のスクリプトのように記述できます。

test6.sh
--
declare -i a=1 b=2
declare -i c
c=$a+$b
echo "$a + $b = $c"
--

もちろん、Bournシェルのように expr コマンドによる
数値演算も可能です。この場合は、バッククォート(`)
で expr コマンド行を囲んで実行させ、その結果を変数
に代入するオーソドックスなシェルのコマンド文で記述
します。例えば、次のスクリプトのように記述できます。

test7.sh
--
a=1
b=2
c=`expr $a + $b`
echo "$a + $b = $c"
--


==========
文字列演算
==========

bashの変数はブレース構文の中で文字列演算子による値
(文字列)の操作が可能です。変数にデフォルト値を設
定したり、文字列を部分的に取り出したりすることがで
きます。

文字列演算子		処理
----------------------+-----------------------------------
${変数:-デフォルト}	変数がなければデフォルトを返す
${変数:=デフォルト}	変数がなければデフォルトを設定する

パターン照合演算子	処理
-----------------------+---------------------------------------------------------------------
${変数#パターン}	変数値にパターンがあれば最初の最短パターンより前を返す
${変数##パターン}	変数値にパターンがあれば最後の最長パターンより後を返す
${変数%パターン}	変数値にパターンがあれば最初の最短パターンより後を返す
${変数%%パターン}	変数値にパターンがあれば最後の最長パターンより後を返す
${変数/パターン/文字列}	変数値にパターンがあれば最初の最長パターンを文字列に置換する
${変数//パターン/文字列}	変数値にパターンがあればすべての最長パターンを文字列に置換する


以下にパターン照合の例を示します。

例) filepath=/opt/apache/conf/httpd.conf.template に対するパターン照合

${filepath}		/opt/apache/conf/httpd.conf.template
${filepath##/*/}	                 httpd.conf.template
${filepath#/*/}	             apache/conf/httpd.conf.template
${filepath%.*}	        /opt/apache/conf/httpd.conf
${filepath%%.*}		/opt/apache/conf/httpd



================
シグナルトラップ
================

Bournシェルゆかりの機能が出てきたついでに、シグナ
ルのトラップについてもふれておきます。trapコマンド
はシグナルを受けとった時に実行するディスパッチコマ
ンドを指定できます。例えば、実行中に途中でキャンセ
ルしたときにメッセージを出して終了させたい場合は次
のように書きます。フォアグラウンドで実行中のプログ
ラムのキャンセルは、コントロールキーとCキーを同時
に押してINT(割り込み)シグナルを送ることで(^Cと
記述します)、通常できます。

test8.sh
--
#!/bin/bash
trap "echo 'INT signal recieved';exit" INT
while true; do
  ps ax | grep $0 | grep -v grep
  sleep 3
done
--

このプログラムの実行はファイルに実行許可を与えて
(chmod +x test8.sh)、直接実行してください。そうし
なければ、親のbashプロセスから抜けることができなく
なり、実行中の端末をkillし(終了させ)なくてはなら
なくなります。

	--
	$ ./test8.sh
	 5133 pts/0    S      0:00 /bin/bash ./test8.sh
	^C
	INT signal recieved
	--

もちろん、trapの2つめの引数にはファンクションも指
定することができますので、次のように記述することも
可能です。INTシグナルの場合は、exitで終了しなけれ
ば、イベントの表示だけを行なうことも可能です。

test9.sh
--
#!/bin/bash
function dispatch
{
  echo 'INT signal recieved'
}

trap dispatch INT
while ( true ) do
  ps ax | grep $0 | grep -v grep
  sleep 3
done
--

この場合に実行端末からプログラムを中止するにはコン
トロールキーとZキーを同時に押して(^Zと記述します)
一旦停止して、バックグラウンドプロセスにしてから終
了させることができます。

	$ ./test9.sh
	 5359 pts/0    S      0:00 /bin/bash ./test9.sh
	INT signal received
	 5359 pts/0    S      0:00 /bin/bash ./test9.sh
	INT signal received
	 5359 pts/0    S      0:00 /bin/bash ./test9.sh
	 5359 pts/0    S      0:00 /bin/bash ./test9.sh
	^Z
	[1]+  Stopped                 ./test9.sh
	$ bg
	[1]+ ./test9.sh &
	$ kill %
	[1]+  終了しました            ./test9.sh

シグナルの名前については man kill を実行して確認して
ください。


========
制御構造
========

すでに出てきたのですが、while 文は bash の繰返し処
理の制御を行ないます。構文は次のように while に続
けて条件を記述し、それに続く行には do と done の行
にはさんで処理を記述します。

--
while 条件
do
    処理内容
done
--

たとえば、次のスクリプトは引数からパラメータを得る
ためのものです。


test10a.sh
--
#!/bin/bash
while [ "$#" -gt 0 ]
do
  case $1 in
    -h|--help|-\?)
      echo "This is help message."
      exit 0
      ;;
    -n|--max)
      max=$2
      shift
      ;;
    *)
      echo "$0: invalid option or parameter: $1" 1>&2
      exit 1
      ;;
  esac
  shift
done
echo "max=$max"
--

shift命令は、$2の値を$1へ、$3の値を$2へというよう
に引数を一つずつシフトします。いくつかオプションを
与えて実行してみると次のようになります。

	$ ./test10a.sh miss
	test10a.sh: invalid option or parameter: miss
	$ ./test10a.sh --help
	This is help message.
	$ ./test10a.sh -n 2
	max=2


bashでは ; を実行命令と解するので、次のように書く
こともできます。

--
while 条件; do
    処理内容
done
--

while文では条件が真の間は処理が行なわれるのですが、
これに対して、until文では条件が偽の間は処理が行な
われます。

--
until コマンド; do
    処理内容
done
--

基本的なLinux(UNIX)のコマンドは処理が正常に終了す
ると 0 の状態コードを返し、すなわち偽となるので、
until はコマンドが正常終了するまで、コマンドの実行
を繰り返したいときに便利です。

もうひとつの繰返し処理のために for 文があります。
for 文ではリストから項目をひとつずつ取り出して指定
した一時変数に格納して処理を行なうことができます。

for 一時変数名 in リスト
do
	処理内容
done

例えば、次のスクリプトはそのディレクトリにある
拡張子が .txt のファイルをすべて EUC に変換し
て、もとのファイル名に .euc を付け加えた名前の
ファイルに出力します。

test10.sh
--
#!/bin/bash
for file in *; do
  nkf -e $file > $file.euc
done
--


もう少し、いろいろと条件を付けて処理を行ないたい場
合には if 文による判定を加えることができます。if 
ブロックの基本的な形は、

if 条件
then
  処理内容
fi

ですが、then と fi の間には、 elif と else のブロッ
クを加えることもできます。if 文の条件のところには
コマンドを記述してその終了時のステータス値を真偽の
判定条件にすることができます。条件には [ と ] とで
括った条件式、もしくは、ステータスを返すコマンドが
使えます。

test11.sh 
--
#!/bin/bash
if [ -f /etc/passwd ]; then
  if grep "^www:" /etc/passwd; then
    echo "www is existed"
  else
    echo "www is not existed"
  fi
fi
--

この条件の [ -f /etc/passwd ] は、/etc/passwd とい
う通常ファイルが存在するかどうかを調べます。この
'-f' はファイル属性演算子と言い、ほかの演算子も
含めて以下の属性を調べられます。 


演算子		調べる属性
 -d		ディレクトリ
 -e		存在する
 -f		通常ファイル
 -r		読み込み可能
 -s		空ではない
 -w		書き込み可能
 -x		実行可能
 -O		所有者に一致
 -G		グループIDが一致

また、以下の演算子でファイルの更新時刻の比較を行な
える。

演算子		
 -nt		左側のファイルは右側のファイルより新しい
 -ot		左側のファイルは右側のファイルより古い


このスクリプトは次のように書くこともできます。

test12.sh
--
#!/bin/bash
if [ -f /etc/passwd ] && grep "^www:" /etc/passwd; then
  echo "www is existed"
else
  echo "www is not existed"
fi
--

2つの if文の条件を論理演算子の && を使って併せて
います。論理演算子には || もあります。&& は論理積
(かつ)を表し、|| は論理和(または)を表します。
また、条件の前に ! を置くと否定になります。論理演
算子にはこれらの他に、[ と ] とで囲まれた条件式の
中で利用可能な論理積 -a と論理和 -o もあります。条
件式の中でのグループ化は \(バックスラッシュ)でエ
スケープした \( と \) とで括ることで表せます。

条件式の中で、以下の比較演算子を使うと文字列の比較を
おこないます。

 文字列演算子		意味
 -------------+----------------------------------------
   =		左右の文字列が同じ
  !=		左右の文字列は異なる
   <		左側の文字列のほうが辞書では先に現れる
   >		左側の文字列のほうが辞書では後に現れる

このため、数値として比較したい場合は以下の演算子を
使う必要があります。

 数値演算子		意味
 -------------+----------------------------------------
    -eq		等しい
    -ne		等しくない
    -lt		より小さい
    -gt		より大きい
    -le		以下
    -ge		以上


なお、さきほどのスクリプトを実行すると、www がパス
ワードファイルに存在するときに、次のように grep の
出力も表示されてしまいます。

	--
	$ ./test12.sh
	www:x:10005:101::/home/www:/opt/pgsql/bin/pgbash
	www is existed
	--

プログラムの出力を表示したくない場合は、/dev/null 
へ出力をリダイレクトします。エラー出力も表示したく
ない場合はエラー出力ユニット番号の 2 を標準出力ユ
ニット番号の 1 へリダイレクトする記述も加えます。
たとえば、次のようにします。

test12.sh
--
#!/bin/bash
if [ -f /etc/passwd ] && grep "^www:" /etc/passwd 1> /dev/null 2>&1; then
  echo "www is existed"
else
  echo "www is not existed"
fi
--

これで、grep コマンドの出力は表示されなくなりました。

	--
	$ ./test12.sh
	www is existed
	--

======
デバグ
======

筆者はデバッガも使わずひたすら出力文(echo)でデバグ
をする始末です。あえて、使う簡単な方法はbashの実行
オプションの -x を使って、実行している行を表示させ
ることです。とはいえ、bashにもbashdbという専用デバッ
ガはあるそうですので、本格的にプログラムを書く際は
使ってみてください。


==================
サンプルプログラム
==================

解説したいくつかの機能を使ったサンプルスプログラム
としてPlamo Linux上で PostgreSQLパッケージを作成す
るためのスクリプトを紹介します。build.pgsql73 とい
うスクリプトがそれです。このスクリプトは 
./custom/pgsql/ ディレクトリに、インストーラ 
doinst.sh73 とインストーラがインストールのときにメ
モリサイズの設定に使うスクリプト
(get_memory_parameters)が用意されていることを前提
にしています。

build.pgsql73
========================================================================
     1	#!/bin/bash
     2	# PostgreSQL
     3	#	BuildScript for Plamo Linux 3.x
     4	#			by Jun Kuwamura  on 2003-07-10
     5	#
     6	umask 022
     7	CWD=`pwd`
     8	WORK=$CWD/work
     9	rm -rf $WORK/*
    10	mkdir -p  $WORK
    11	export LANG=C
    12	export CFLAGS="-O2"
    13	export PATH=$PATH:/usr/X11R6/bin
    14	
    15	SRC_URI="ftp://ftp.postgresql.org/pub/source/v7.3.3/postgresql-7.3.3.tar.gz"
    16	FAQ_URI="http://www.postgresql.jp/subcommittee/jpugdoc/faq-japanese.txt"
    17	
    18	SRC_FILE=${SRC_URI##*/}
    19	FAQ_FILE=${FAQ_URI##*/}
    20	MSG_FILE=${MSG_URI##*/}
    21	SRC_DIR=$CWD/archive/pgsql
    22	mkdir -p $SRC_DIR
    23	PAC_NAME=${SRC_FILE%%.tar.gz}
    24	PAC_DIR=$CWD/package/Database
    25	mkdir -p $PAC_DIR
    26	CUSTOM_DIR=$CWD/custom/pgsql
    27	mkdir -p $CUSTOM_DIR
    28	
    29	DOC_DIR=/usr/doc/$PAC_NAME
    30	PREFIX_DIR=opt
    31	
    32	REL=1
    33	SYS=i386
    34	SYS_NAME="${SYS}-${REL}"
    35	
    36	if [ ! -f $SRC_DIR/$SRC_FILE ]; then
    37	    (cd $SRC_DIR; wget $SRC_URI);
    38	fi
    39	(cd $CUSTOM_DIR; wget -N $FAQ_URI);
    40	
    41	if [ -d /$PREFIX_DIR/pgsql ] ; then
    42	  mv /$PREFIX_DIR/pgsql /$PREFIX_DIR/pgsql.bak
    43	fi
    44	rm -f /$PREFIX_DIR/pgsql
    45	
    46	# making main package
    47	tar xvfz $SRC_DIR/$SRC_FILE
    48	cd $PAC_NAME
    49	./configure  --prefix=/$PREFIX_DIR/pgsql \
    50		--enable-nls --enable-syslog --with-openssl=/usr
    51	make
    52	make install
    53	
    54	# install documents
    55	mkdir -p $WORK/$DOC_DIR
    56	cp -a COPYRIGHT HISTORY INSTALL README register.txt config.status \
    57		doc/FAQ* doc/README* doc/TODO* doc/KNOWN_BUGS doc/MISSING_FEATURES \
    58		doc/bug.template $WORK/$DOC_DIR
    59	cp -f $CUSTOM_DIR/$FAQ_FILE $WORK/$DOC_DIR/FAQ_japanese
    60	chown -R root.root $WORK/$DOC_DIR
    61	chmod -R a+rX,go-w $WORK/$DOC_DIR
    62	
    63	# packing
    64	cd $WORK
    65	tar cfp - /$PREFIX_DIR/pgsql | tar xvfp -
    66	mv opt/pgsql opt/$PAC_NAME
    67	
    68	cp opt/$PAC_NAME/share/pg_hba.conf.sample opt/$PAC_NAME/share/pg_hba.conf.template
    69	cat >>opt/$PAC_NAME/share/pg_hba.conf.sample<<__EOCF
    70	# Allow login user access to the database which name is the same 
    71	# as the username
    72	#local      sameuser                                     trust
    73	
    74	# Allow local network user whos password in the admin file
    75	#host       all         192.168.2.0   255.255.255.128    md5      admin
    76	# Allow local network and password  created with pg_passwd passwd
    77	# command under data directory if you uncomment the followings.
    78	#hostssl    all         192.168.2.0   255.255.255.128    password passwd
    79	#hostssl    all         0.0.0.0       0.0.0.0            krb5
    80	__EOCF
    81	
    82	mkdir -p install
    83	sed -e "s/PGSQLDIR/$PAC_NAME/g" -e "s/PREFIXDIR/$PREFIX_DIR/g" $CUSTOM_DIR/doinst.sh73 > install/doinst.sh
    84	
    85	mkdir -p opt/$PAC_NAME/apps/utils
    86	cp $CUSTOM_DIR/get_memory_parameters opt/$PAC_NAME/apps/utils/
    87	chmod +x opt/$PAC_NAME/apps/utils/get_memory_parameters
    88	
    89	# make package
    90	echo "n
    91	" | installpkg -m $PAC_NAME
    92	mv ${PAC_NAME}.tgz  $PAC_DIR/${PAC_NAME}-${SYS_NAME}.tgz
    93	echo "$PAC_DIR/${PAC_NAME}-${SYS_NAME}.tgz ... done"
    94	cd $CWD
========================================================================

まず、6〜13行目で作業環境の初期化をしています。
17,18行めでインターネットからダウロードするソース
ファイルを指定しています。実際にダウンロードを試み
るのは34〜38行目です。21〜32行目は主にスクリプト内
で利用する変数の設定をしています。URIの文字列から
ファイル名だけを取り出したり、さらに、拡張子を取り、
部分的に抜き出してパッケージ名にしたりしています。
作業中にインストールする場所の確保を41〜44でします。
そして、いよいよアーカイブを展開してプログラムのメ
イクとインストールを47〜52行目あたりでやってます。
55〜61行目ではドキュメント類をパッケージング用の作
業ディレクトリへインストールします。64行目からパッ
ケージにするファイルを作業ディレクトリに準備します。
68行目からは設定ファイルをヒヤドキュメントにして作っ
ています。83〜87行目はインストーラの準備です。
最後に90行目からinstallpkgプログラムを使って
パッケージを作成しています。

参考までに、./custom/pgsql/ ディレクトリに用意する
インストーラのソースを示します。

doinst.sh73
========================================================================
     1	#!/bin/sh
     2	# This should be done at initial booting after installation.
     3	#
     4	export LANG=C
     5	if [ -r /tmp/SeTT_PX ]; then
     6	  ROOT="`cat /tmp/SeTT_PX`"
     7	fi
     8	
     9	cd $ROOT/
    10	
    11	if [ -d $ROOT/PREFIXDIR/pgsql ] ; then
    12	  mv $ROOT/PREFIXDIR/pgsql $ROOT/PREFIXDIR/pgsql.bak
    13	fi
    14	rm -f $ROOT/PREFIXDIR/pgsql
    15	ln -sf $ROOT/PREFIXDIR/PGSQLDIR $ROOT/PREFIXDIR/pgsql
    16	
    17	(cd $ROOT/usr/doc/PGSQLDIR && \
    18	 rm -f doc && \
    19	 ln -s $ROOT/PREFIXDIR/PGSQLDIR/doc doc )
    20	
    21	#
    22	# Create Environment
    23	#
    24	PG_ROOT=$ROOT/PREFIXDIR/PGSQLDIR; export PG_ROOT
    25	
    26	# Add user and group
    27	groupadd pgsql
    28	useradd -g pgsql -d $ROOT/PREFIXDIR/pgsql -c "Postgres Admin." -s /bin/tcsh postgres
    29	chown -R postgres.pgsql $ROOT/PREFIXDIR/PGSQLDIR
    30	
    31	# This is required for Kerberos authentication
    32	if ! fgrep "postgres" /etc/services 1> /dev/null 2>&1; then
    33	    echo 'postgres	5432/tcp			# Reserve for PostgreSQL' >> /etc/services
    34	else
    35		echo "Entry postgres is already in /etc/services."
    36	fi
    37	
    38	if [ ! -f /etc/rc.d/init.d/postgresql ]; then
    39	  cp /PREFIXDIR/pgsql/etc/init.d/postgresql /etc/rc.d/init.d/
    40	  chmod +x /etc/rc.d/init.d/postgresql
    41	fi
    42	
    43	if ! fgrep "/PREFIXDIR/pgsql/lib" $ROOT/etc/ld.so.conf 1> /dev/null 2>&1; then
    44	  echo "/PREFIXDIR/pgsql/lib" >> $ROOT/etc/ld.so.conf
    45	  if [ "$ROOT" = "" ] ; then
    46	    /sbin/ldconfig
    47	  fi
    48	else
    49	  echo "Entry /PREFIXDIR/pgsql/lib is already in $ROOT/etc/ld.so.conf."
    50	fi
    51	
    52	# add LOG_LOCAL1 for syslogd report
    53	if ! fgrep "/var/log/pgsqlog" $ROOT/etc/syslog.conf 1> /dev/null 2>&1; then
    54	  echo "local1.*		/var/log/pgsqlog" >> $ROOT/etc/syslog.conf
    55	  if [ ! -f $ROOT/var/log/pgsqlog ]; then
    56	    touch $ROOT/var/log/pgsqlog
    57	  fi
    58	  if [ "$ROOT" = "" ] ; then
    59	    kill -HUP `pidof syslogd`
    60	  fi
    61	else
    62		echo "Entry pgsqlog is already in /etc/syslog.conf."
    63	fi
    64	if [ -f $ROOT/etc/logrotate.conf ]; then
    65	  if ! fgrep "/var/log/pgsqlog" $ROOT/etc/logrotate.conf 1> /dev/null 2>&1; then
    66	    cat >> $ROOT/etc/logrotate.conf <<__LOGROT
    67	# postgresql's syslog output rotation
    68	/var/log/pgsqlog {
    69	        rotate 5
    70	        postrotate
    71	           /bin/killall -HUP syslogd
    72	        endscript
    73	}
    74	__LOGROT
    75	  fi
    76	fi
    77	
    78	if ! fgrep "Add by Plamo Linux 3.x" $ROOT/PREFIXDIR/PGSQLDIR/share/postgresql.conf.sample 1> /dev/null 2>&1; then
    79	  cat >> $ROOT/PREFIXDIR/PGSQLDIR/share/postgresql.conf.sample <<__PGCONF1
    80	
    81	#
    82	# Add by Plamo Linux 3.x installer
    83	syslog = 2
    84	syslog_facility = 'LOCAL1'
    85	syslog_ident = 'postgres'
    86	tcpip_socket = true
    87	ssl = true
    88	
    89	__PGCONF1
    90	
    91	  $ROOT/PREFIXDIR/PGSQLDIR/apps/utils/get_memory_parameters -n 64 \
    92		>>$ROOT/PREFIXDIR/PGSQLDIR/share/postgresql.conf.sample
    93	fi
    94	
    95	# set access control file(open for your network)
    96	NADDR=`ifconfig eth0 | fgrep inet | sed -e 's/.*addr:\(.*\) .*Bcast:\(.*\) .*Mask:\(.*\)/\1/'`
    97	NMASK=`ifconfig eth0 | fgrep inet | sed -e 's/.*addr:\(.*\) .*Bcast:\(.*\) .*Mask:\(.*\)/\3/'`
    98	if [ "$NADDR" != "" ] ; then
    99		sed -e "s/192.168.2.0/$NADDR/" -e "s/255.255.255.128/$NMASK/" \
   100	 $ROOT/PREFIXDIR/PGSQLDIR/share/pg_hba.conf.template > $ROOT/PREFIXDIR/PGSQLDIR/share/pg_hba.conf.sample
   101	fi
   102	
   103	# Add enviroment variables to rc files
   104	#  for tcsh
   105	if [ ! -f $ROOT/PREFIXDIR/PGSQLDIR/.cshrc ]; then
   106	  cat > $ROOT/PREFIXDIR/PGSQLDIR/.cshrc <<__CSHRC
   107	setenv PG_DIR   /PREFIXDIR/PGSQLDIR
   108	setenv PGDATA   ${PG_DIR}/data
   109	setenv PGLIB    ${PG_DIR}/lib
   110	#setenv PGPORT  5432
   111	#setenv PGHOST  localhost
   112	if (! $?LD_LIBRARY_PATH) then
   113	  setenv LD_LIBRARY_PATH $PG_DIR/lib:/usr/X11R6/lib
   114	else
   115	  setenv LD_LIBRARY_PATH $PG_DIR/lib:${LD_LIBRARY_PATH}
   116	endif
   117	if (! $?MANPATH) then
   118	  setenv MANPATH ${PG_DIR}/man:/usr/man:/usr/share/man:/usr/X11R6/man
   119	else
   120	  setenv MANPATH ${PG_DIR}/man:${MANPATH}
   121	endif
   122	set path=( ${PG_DIR}/bin  $path )
   123	__CSHRC
   124	fi
   125	#  for bash
   126	if [ ! -f $ROOT/PREFIXDIR/PGSQLDIR/.bashrc ]; then
   127	  cat > $ROOT/PREFIXDIR/PGSQLDIR/.bashrc <<__BASHRC
   128	PGROOT=/PREFIXDIR/PGSQLDIR
   129	export PGDATA=$PGROOT/data
   130	export PGLIB=$PGROOT/lib
   131	#export PGHOST=localhost
   132	#export PGPORT=5432
   133	
   134	export PATH=$PGROOT/bin:$PATH
   135	export LD_LIBRARY_PATH="$PGROOT/lib:${LD_LIBRARY_PATH-/usr/X11R6/lib}"
   136	export MANPATH="$PGROOT/man:${MANPATH-/usr/share/man:/usr/X11R6/man}"
   137	__BASHRC
   138	fi
   139	
   140	#
   141	# ( DONOT CHANGE THE NEXT ONE LINE )
   142	# Add PostgreSQL Entry to Apache document index.
   143	#
   144	if [ -f $ROOT/PREFIXDIR/apache/htdocs/contents.list ]; then
   145	  if ! fgrep "PGSQLDIR" $ROOT/PREFIXDIR/apache/htdocs/contents.list 1> /dev/null 2>&1; then
   146	    cat >> $ROOT/PREFIXDIR/apache/htdocs/contents.list <
   149	    PGSQLDIR
   150	    (
   151	       http://www.jp.postgresql.org/ 
   154		|
   155	       http://www.postgresql.jp/ 
   158	    )
159 (Object Relational Database Management System)
160 documentation 163

164 EOL 165 fi 166 if [ ! -f $ROOT/PREFIXDIR/apache/htdocs/PGSQLDIR ]; then 167 ln -sf $ROOT/usr/doc/PGSQLDIR $ROOT/PREFIXDIR/apache/htdocs/PGSQLDIR 168 fi 169 else 170 awk '/Add PostgreSQL Entry/,EOF' $ROOT/install/doinst.sh >> /tmp/add_apache_test.sh 171 fi get_memory_parameters ======================================================================== 1 #!/bin/bash -f 2 # 3 # Calculate memory parameters for PostgreSQL-7.1 |!あ|on Linux. 4 # 5 # This program calculate sample values of "sort_mem" and "shared_buffers" 6 # in $PGDATA/postgresql.conf for your Linux environment according to your 7 # Linux configurated parameters. 8 # 9 # Author: Jun Kuwamura 10 # Creation: 2001-04-11 11 # 12 bug_report_to="juk@yokohama.email.ne.jp" 13 # 14 CMDNAME=`basename $0` 15 16 # set default and parameter 17 ##max_connections = 32 # 1-1024 18 max_connections=32 19 denominator=2 20 21 help="\ 22 $CMDNAME: Calculate memory parameters for PostgreSQL-7.1 23 24 Usage: 25 $CMDNAME [-n max_connections] 26 where; 27 max_connections: number of maximum connections 28 [default $max_connections] 29 30 Report bugs to <$bug_report_to>." 31 advice="\ 32 Try '$CMDNAME --help' for more information." 33 34 35 while [ "$#" -gt 0 ] 36 do 37 case $1 in 38 -h|--help|-\?) 39 echo "$help" 40 exit 0 41 ;; 42 -n|--max_connections) 43 max_connections=$2 44 shift 45 ;; 46 *) 47 echo "$CMDNAME: invalid option or parameter: $1" 1>&2 48 echo "$advice" 1>&2 49 exit 1 50 ;; 51 esac 52 shift 53 done 54 55 if [ $max_connections -lt 1 -o $max_connections -gt 1024 ]; then 56 echo "max_connections must be between 1 and 1024" 57 exit 58 fi 59 60 ##sort_mem = 512 # [k bytes] 61 #sort_mem = 1024 # core_mem / 10 / max_connections 62 # 63 core_mem=`free | grep Mem: | awk '{print $2}'` 64 sort_mem=`expr $core_mem / $denominator / $max_connections` 65 66 67 ##shared_buffers = 2*max_connections # min 16 [8k bytes] 68 #shared_buffers = 2048 # [8k bytes] 69 shm_max_bytes=`cat /proc/sys/kernel/shmmax` 70 shm_max=`expr $shm_max_bytes / 1024` 71 shared_buffers=`expr $shm_max / $denominator / 8` 72 73 # check minimum number of buffers 74 shared_buffers_min=`expr $max_connections \* 2` 75 if [ $shared_buffers -lt $shared_buffers_min ]; then 76 shared_buffers=$shared_buffers_min 77 fi 78 79 echo "max_connections = $max_connections" 80 echo "sort_mem = $sort_mem" 81 echo "shared_buffers = $shared_buffers" ======================================================================== 参考文献 「入門bash第2版」, ISBN4-900900-78-8
サンプルプログラムのダウンロード