<シェルスクリプトを覚えよう!>
当サイトで提供するシェルスクリプトは、Borne shellで提供しています。Borne shell以外には、Cシェルでのシェルスクリプトがありますが、あまりお薦めできません。理由は次のURLを参照して下さい。
http://www.klab.ee.utsunomiya-u.ac.jp/~hiroki/csh-whynot.euc
尚、上記解説はBorneシェルとCシェルを知り尽くした方向けです。簡単にBorneシェル、Cシェルの違いを例えるなら、標準語と大阪弁みたいに方言のようなものです。慣れの問題なので個人の趣味で選択されていると思います。
ここでは、当サイトのHOW-TO集番外編で提供しているaddftpuserを基に中身の処理について解説します。参考までにシェルスクリプトとは、起動するコマンドを集めてテキストファイルに保存し、そのファイルに実行権を与えることによって、順次指定したコマンドを起動することができます。また、シェルスクリプトに記述されたコマンドを手で一つずつ起動しても同じ動作となります。この為、この中身を把握することは、起動するコマンドを覚え、利用するコマンドのレパートリーが増えることになります。GUIによる管理は楽なのですが、是非サーバーの管理者への第一歩として各種コマンドを覚えて活用して頂きたいと思います。
#!/bin/sh
【解説01】シェルスクリプトの1行目で"#!"が指定されている場合は特別な意味を持ち、
指定したシェルやプログラムで動作させることを意味します。
一般的には、以下の様な指定が使われます。
#!/bin/sh Borne shellでスクリプトを処理
#!/bin/csh C shellでスクリプトを処理
#!/usr/bin/perl perlでスクリプトを処理
尚、この指定が無い場合、シェルスクリプトはカレントのシェルで処理します。
この為、カレントシェルがBorneシェルで、先頭に"#!/bin/csh"が記述されて
いないCシェルスクリプトを起動すると構文エラーとなる可能性があります。
#
# OIDEN FTP-Only User Registration Scripts
# http://www.oiden.net 2000/12/05(Ver.1.01)
#
【解説02】上記4行の'#'より右側に記述された文字はコメントとして扱われます。
PATH=${PATH}:/usr/sbin
【解説03】PATH環境変数に/usr/sbinを追加しています。環境変数に値をセットする時は、
'='の前後に空白を入れるとエラーとなりますので注意して下さい。
PATH環境変数は、起動するコマンドを探す時のパスが記述されています。
'echo ${PATH}'を実行してみて下さい。デフォルトのパス指定が表示されます。
複数のパス指定は、':'で区切られています。デフォルトで/usr/sbinの
ディレクトリ指定が含まれているのであれば、この指定は不要です。
尚、現在どの様な環境変数が設定されているかは'set'コマンドで確認できます。
PASSWD=/etc/passwd
【解説04】PASSWD環境変数に"/etc/passwd"の文字列を代入しています。
環境変数の値の代入は、'='を空白なしで指定して、代入した環境変数を参照する
場合は、環境変数の頭に'$'(ダラーマーク)を付けます。以下に使用例を挙げます。
ROOT# LBIN=/usr/local/bin ← LBIN環境変数に"/usr/local/bin"をセットします。
ROOT# echo LBIN ← LBINの頭に"$"を付けずにechoすると、
LBIN ← 結果は"LBIN"を表示します。
ROOT# echo ${LBIN} ← "$"を付けてechoすると、
/usr/local/bin ← LBIN環境変数の値が参照できます。
ROOT# cd ${LBIN} ← 例えば、cdで${LBIN}を指定すると、
ROOT# pwd
/usr/local/bin ← LBIN環境変数でセットされたディレクトリへ移れます。
GROUP=/etc/group
【解説05】GROUP環境変数に"/etc/group"の文字列をセットしています。
INETD_CONF=/etc/inetd.conf
【解説06】INETD_CONF環境変数に"/etc/inetd.conf"の文字列をセットしています。
FTPACCESS=/etc/ftpaccess
【解説07】FTPACCESS環境変数に"/etc/ftpaccess"の文字列をセットしています。
FTPGROUP=ftponly
【解説08】FTPGROUP環境変数に"ftponly"の文字列をセットしています。
PROG=`basename ${0}`
【解説09】PROG環境変数にスクリプト名をセットしています。ここでPROGに与える指定が、
"`"(バッククオート)で囲まれています。これは囲まれた指定を実行し、その結果を
使うことを意味します。
basenameコマンドは、'man basename'で調べて見て下さい。フルパス名のサフィックスを
抽出します。例えば、'basename /aaa/bbb/ccc'を実行すると"ccc"が表示されます。
次にbasenameに引数として与えている${0}は何を表すかというと、起動されたスクリプトの
ファイル名が入ります。また、${1}は第1引数、${2}は第2引数...となります。
例えば、以下のサンプルスクリプトをabcという名前で保存して実行権を与え/usr/local/bin
ディレクトリに置いてみて下さい。
ROOT# cat abc
#!/bin/sh
echo ${0} ${1} ${2} ${3}
ROOT# chmod 0755 abc
ROOT# cp abc /usr/local/bin
次に、abcを適当な引数を指定して起動してみます。
ROOT# abc opt1 opt2 opt3
/usr/local/bin/abc opt1 opt2 opt3
これじゃ、ちょっと解かりにくいので、デバックモードでもう一回!
ROOT# sh -x /usr/local/bin/abc aa bb cc
+ echo /usr/local/bin/abc aa bb cc
/usr/local/bin/abc aa bb cc
デバックモードでは実行されたコマンドが先頭に'+ 'が付加されて表示されます。これを見ると
echoコマンドで、${0},${1},${2},${3}に対応した引数の値が解かり易いと思います。
と、いうことでチョット寄り道しましたが、${0}には"/usr/local/bin/addftpuser"の文字列が
入り、この文字列からbasenameコマンドでサフィックスを抽出するので、PROG環境変数には
"addftpuser"がセットされます。
if [ "${LANG}" = "ja_JP.eucJP" ] ; then
JMSG=1;
else
JMSG=0;
fi
【解説10】この5行はif文です。最も基本的な文法ですので是非覚えて下さい。文法は以下の通りです。
if [ 条件 ] ; then
条件が一致した時の処理
else
条件が不一致の時の処理
fi
条件判別では、文字列を比較する"="や"!="、数値を比較する-eq,-ne,-gt,-ge,-lt,-le等が利用
できます。else文は省略しても構いません。
ここでは、LANG環境変数が"ja_JP.eucJP"と一致している時にJMSG環境変数に"1"をセットし、
それ以外であれば、JMSG環境変数に"0"をセットしています。JMSG環境変数は、"1"の場合には
日本語EUCによるメッセージ、"0"であれば英語によるメッセージを出力をする判定フラグとして
利用しています。
if [ $# -ne 1 -a $# -ne 2 ] ; then
if [ ${JMSG} -eq 1 ] ; then
echo "構文エラー:${PROG} 登録ログイン名 [ログインディレクトリ名
else
echo "Usage: ${PROG} login_name [login_directory]"
fi
exit 1
fi
【解説11】この8行はaddftpuserに指定されたオプションの数をチェックしています。
$#環境変数は与えられた引数の数が入り、最初のif文の条件で、$#が"1"でもなく"2"でも
ない場合、次のif文でJMSG環境変数で日本語か英語のメッセージ出力後、exit 1で強制終了
させます。
既に当サイトのaddftpuserスクリプトを/usr/local/binに設定されている方は以下コマンドを
起動してみて下さい。
ROOT# sh -x /usr/local/bin/addftpuser a b c
addftpuserへのオプション指定は3つなので、以下のメッセージで異常終了するはずです。
+ [ 3 -ne 1 -a 3 -ne 2 ]
+ [ 1 -eq 1 ]
+ echo 構文エラー:addftpuser 登録ログイン名 [ログインディレクトリ名]
構文エラー:addftpuser 登録ログイン名 [ログインディレクトリ名]
+ exit 1
FLAG=`id -u`
【解説12】これも【解説09】と同様に代入が"`"(バッククオート)で囲まれています。ということは、
id -uの結果をFLAG環境変数にセットしています。man idでidコマンドの内容を知ることが
できますが、idで-uオプションを指定すると、カレントのユーザIDを表示します。
ここで期待しているのは、rootのユーザIDである0です。root以外のユーザIDは/etc/passwd
ファイルの第3セパレータで確認できますが、0以外の数値となります。
if [ ${FLAG} -ne 0 ] ; then
if [ ${JMSG} -eq 1 ] ; then
echo "ERROR: ${PROG}はroot権限で実行して下さい。"
else
echo "ERROR: ${PROG} must be root permission."
fi
exit 1
fi
【解説13】この8行はid -uの結果がセットされたFLAGの内容を参照し、FLAGが0(rootのユーザID)以外の
時にエラーメッセージを出力して処理を終了します。
USER=$1
CDIR=$2
【解説14】この2行は【解説09】のオプション指定で説明していますが、第1引数で指定されたオプションを
USER環境変数、第2引数で指定されたオプションをCDIR環境変数にセットしています。
FLAG=`grep "^ftp" ${INETD_CONF}`
【解説15】ここではFLAG環境変数にgrepの結果を入れ直しています。【解説12】でセットされた値は上書き
されます。grepコマンドは第1引数で指定されたパターンを第2引数以降(複数のファイル可能)
で指定されたファイルの中から抽出して表示します。
パターンとして"^ftp"を指定していますが、最初の"^"は特殊記号であり、行の先頭を意味
します。例えば、以下の内容のファイルをこのパターンで検出すると2行目と4行目が表示され
ます。
abc ftp
ftp
fftp
ftp data
第2引数として${INETD_CONF}が与えられていますが、【解説06】でセットした/etc/inetd.confが
参照されますので、このFLAG環境変数には、/etc/inetd.confファイル内で行の先頭文字が
ftpで始る行がセットされます。
if [ "${FLAG}" = "" ] ; then
if [ ${JMSG} -eq 1 ] ; then
echo "ERROR: ${INETD_CONF}でftpサービスが有効でありません。"
else
echo "ERROR: ftp service disabled in ${INETD_CONF}."
fi
exit 1
fi
【解説16】この8行は、/etc/inetd.conf内で行の先頭文字がftpで始る行が存在しない、すなわち、ftp
サービスが有効でないシステムの場合、エラーメッセージを出力してエラー終了します。
RPMCHK=0
【解説17】RPMCHK環境変数に0をセットしています。
FLAG=`rpm -qa|grep "^ftp-"`
【解説18】FLAG環境変数にrpmの一覧出力の中で行の先頭が"ftp-"で始る出力結果が存在する場合はその行を、
存在しない場合は空白をセットします。ここでは"|"(パイプ)が利用されています。シェルではパイプ
がよく活用されます。とても便利なので是非覚えて下さい。
パイプとは標準出力と標準エラー出力をパイプの右側に指定されたコマンドの標準入力として渡す
ことが出来ます。簡単な例として、
・プロセス一覧リストの中からhttpdプロセスのみ出力を出力したい場合
ps -ef|grep httpd
・パイプを2つ利用してシステム内にhttpdがいくつ実行されているか調べたい場合
ps -ef|grep httpd|wc -l
if [ "${FLAG}" = "" ] ; then
RPMCHK=1
fi
【解説19】この3行はFLAG環境変数にセットされた結果が空白、すなわちシステムにftpモジュールがインストール
されていない場合、RPMCHK環境変数の数値を0から1に変更します。
FLAG=`rpm -qa|grep "^wu-ftpd-"`
【解説20】ここでは【解説18】と同様にrpmの一覧出力の中で行の先頭が"wu-ftpd-"で始る出力結果が存在する
場合はその行を、存在しな場合は空白がセットされます。
if [ "${FLAG}" = "" ] ; then
RPMCHK=`expr ${RPMCHK} + 2`
fi
【解説21】この3行はFLAG環境変数にセットされた結果が空白、すなわちシステムにwu-ftpモジュールがインス
トールされていない場合、RPMCHK環境変数へ既にセットされているRPMCHK環境変数に2をプラスした
値をセットします。ここでは、exprコマンドを利用して足し算を行っています。詳しい使い方は、
"man expr"を参照して下さい。使用例は以下の通りです。
expr 8 / 2 ← 8/2の結果を表示
ANS=`8 - 2` ← 8-2の結果をANS環境変数へセット
exho ${ANS} ← ANS環境変数へセットされた値を表示
expr 8 \* 2 ← 8*2の結果を表示、掛け算を表す'*'は特殊記号で予約されていますので
特殊記号を無効にするため、\(バックスラッシュ)を直前に指定する必要
があります。
expr 8 * 2 ← ちなみに特殊記号の前にバックスラッシュを指定しないとsyntax error
構文エラーになります。
これ以下未解説です。続きはまたの機会に。。。m(_ _)m
if [ ${RPMCHK} -ne 0 ] ; then
if [ ${JMSG} -eq 1 ] ; then
case ${RPMCHK} in
1) echo -n "ERROR: 事前にftpモジュールを";;
2) echo -n "ERROR: 事前にwu-ftpdモジュールを";;
3) echo -n "ERROR: 事前にftpとwu-ftpdモジュールを";;
esac
echo "インストールして下さい。"
else
case ${RPMCHK} in
1) echo "ERROR: ftp module not installed.";;
2) echo "ERROR: wu-ftpd module not installed.";;
3) echo "ERROR: ftp and wu-ftpd module not installed.";;
esac
fi
exit 1
fi
FLAG=`grep guestgroup ${FTPACCESS}|grep ${FTPGROUP}`
if [ "${FLAG}" = "" ] ; then
echo "guestgroup ${FTPGROUP}" >>${FTPACCESS}
fi
FLAG=`grep "^${FTPGROUP}:" ${GROUP}`
if [ "${FLAG}" = "" ] ; then
groupadd ${FTPGROUP}
if [ $? -ne 0 ] ; then
if [ ${JMSG} -eq 1 ] ; then
echo "ERROR: groupaddコマンド実行中にエラーが発生しまし
else
echo "ERROR: useradd process."
fi
exit 1
fi
fi
useradd -c "${USER}(${FTPGROUP})" -g ${FTPGROUP} -G ${FTPGROUP} ${USER}
if [ $? -ne 0 ] ; then
if [ ${JMSG} -eq 1 ] ; then
echo "ERROR: useraddコマンド実行中にエラーが発生しました。"
else
echo "ERROR: useradd process."
fi
exit 1
fi
LINE=`grep "^${USER}:" ${PASSWD}`
HOME=`echo ${LINE}|cut -f6 -d':'`
chown ${USER}.${FTPGROUP} ${HOME}
chmod 755 ${HOME}
if [ "${CDIR}" != "" ] ; then
FDIR="${HOME}/./${CDIR}"
c=1
for i in `echo ${LINE}|tr ':' ' '` ; do
case ${c} in
1) NEWLINE="${i}";;
6) NEWLINE="${NEWLINE}:${FDIR}";;
*) NEWLINE="${NEWLINE}:${i}";;
esac
c=`expr ${c} + 1`
done
grep -v "^${USER}:" ${PASSWD} > ${PASSWD}.$$
echo "${NEWLINE}" >>${PASSWD}.$$
mv ${PASSWD}.$$ ${PASSWD}
mkdir ${HOME}/${CDIR} 2>/dev/null
chown ${USER}.${FTPGROUP} ${HOME}/${CDIR}
chmod 755 ${HOME}/${CDIR}
fi
for DIR in bin dev lib ; do
mkdir ${HOME}/${DIR} 2>/dev/null
case ${DIR} in
bin) cd /bin
for FILE in chmod gzip ls tar ; do
cp ${FILE} ${HOME}/${DIR}
chmod 111 ${HOME}/${DIR}/${FILE}
done;;
dev) cd /dev
echo zero|cpio -pdumv ${HOME}/${DIR} 2>/dev/null;;
lib) cd /lib
for LIB in `ls ld-* libc-* libc\.* libtermcap*` ; do
echo ${LIB}|cpio -pdumv ${HOME}/${DIR} 2>/dev/null
done;;
esac
done
passwd ${USER}