Bourneシェルスクリプト入門(+bash)
目次
- はじめに
- 対象者
- コメント
- 構造
- コーディング規約
- ハローワールド
- 変数
- メタキャラクタ
- 制御構造
- リダイレクトとパイプ
- データ処理
- ヒアドキュメント
- 関数
- シグナルのトラップ
- API
- デバック
- 実践
- 計算処理の問題点
はじめに
LinuxやMac OSXをはじめたばかりの人を対象としたBourneシェルスクリプト入門です。bashを使いながらSolarisなどでも使えるように互換性の高いシェルスクリプトを数時間で学びます。またリファレンスをして 使えるように、サンプルを豊富に用意しました。
対象者
Bourneシェルスクリプトを学ぶことでプラットフォーム依存性の低いシェルスクリプトをプログラムしたい方が対象者です。
コメント
# シャープがコメントになります。
構造
先頭行には、必ず#!キーワードを記述します。
#!/bin/sh # コメント echo "shellscript"
この#!はシェバング(shebang)と呼びます。昔Unixでは、最初の2バイトが#!だった時には、それ以降に書かれたコマンドで、ファイルを実行するという決まりがあったそうです。つまり上記のファイル名がcomment.shだとした場合以下のように実行したのと同じになります。
$ /bin/sh ./command.sh
コーディング規約
ありません。
ハローワールド
hello.shとして以下の記述をして適当な場所に保存しましょう。
#!/bin/sh echo "hello world"
シェルスクリプトを実行する単純な方法としては2つあります。1つ目は、作ったファイルに実行権限を付けて実行する方法です。2つ目は、シェバングの部分で説明したように、/bin/shを実行して、その引数としてファイルを渡すことです。この場合は読み込み権限のみ持っていれば実行できます。
ファイルに実行権限を付けて実行
chmodコマンドはCHange MODeの略がコマンドになったものです。これでファイルに権限を追加したり削除したりします。次に、hello.shファイルを./を付けて実行します。これは現在のディレクトリにあるhello.shを実行するという意味です。./をつけないと実行できません。
$ chmod 755 hello.sh
./hello.sh
直接シェルを実行
$ /bin/sh ./hello.sh
変数
シェルスクリプトには様々な変数が用意されています。サンプルを実行しながら学んでいきましょう
変数
もっとも単純な変数は、変数名=値という記述で書きます。注意しなければならない点としては、=の間にスペースを入れてはいけないことです。
#!/bin/sh
test_val=100
echo $test_val
シングルクォートとダブルクォートの違い
値をダブルクォートで囲むと変数は値に置き換わります。上記の例と同じ動作です。
#!/bin/sh
test_val=100
echo "$test_val"
実行例
$ ./test.sh
100
シングルクォートで囲むと変数の値は置き換わりません。$test_valが文字列として表示されます。
#!/bin/sh
test_val=100
echo '$test_val'
実行例
$ ./test.sh
$test_val
空白とヌル(null)文字
値に何も書かなくても、変数は定義できます。その場合は長さが0のヌル文字として扱われます。空白文字とヌル文字は違うものなので、条件分岐などの時に間違えやすいので注意しましょう。
#!/bin/sh null_val= # 長さ0の文字 space_val=' ' # これは空白文字 echo $null_val echo $space_val
シェル変数
シェル変数とは、あらかじめシェルが定義している変数のことです。コマンドラインでsetを実行すると一覧が表示できます。
-bash-3.00$ set APACHE_HOME=/usr/local/apache2 BASH=/sw/bin/bash BASH_VERSINFO=([0]="3" [1]="00" [2]="0" [3]="1" [4]="release" [5]="powerpc-apple-darwin7.9.0") BASH_VERSION='3.00.0(1)-release' COLUMNS=150
特殊変数
シェルスクリプトには特殊変数が用意されています。$と記号、または$と数値で構成されます。 如何に様々な特殊変数のサンプルを書きましたので実際に実行してみてください。
spec.sh
#!/bin/sh
# 以下のように実行
# $ sh spec.sh aa bb cc dd ee
echo 'コマンドの終了ステータス'
STATUS=$?
echo 'プロセス番号'
echo $$
echo 'コマンド名'
echo $0
echo '引数の数'
echo $#
echo '1番目の引数'
echo $1
echo '2番目の引数.. $9まで可能で、$10とする場合は${10}と書く'
echo $2
echo '引数全て. "$1 $2 $3 ..."'
echo $*
for ITEM in $*
do
echo $ITEM
done
echo '引数全て "$1" "$2" $3"'
echo $@
for ITEM in $@
do
echo $ITEM
done
実行例
-bash-3.00$ sh spec.sh aa bb cc dd ee
コマンドの終了ステータス
プロセス番号
11430
コマンド名
spec.sh
引数の数
5
1番目の引数
aa
2番目の引数.. $9まで可能で、$10とする場合は${10}と書く
bb
引数全て. "$1 $2 $3 ..."
aa bb cc dd ee
aa
bb
cc
dd
ee
引数全て "$1" "$2" $3"
aa bb cc dd ee
aa
bb
cc
dd
ee
特殊変数の中で、$*,$@は、引数の全てを表示し、$#は引数の数を表示します。各引数を 参照するには、$1,$2のように使います。$0はプログラム名が入っています。また、$と数値 の組み合わせのことを位置パラメータとも呼びます。
$*と$@の違いは、$*は、位置パラメータを1つの文字列とします。$@はスペースで区切られた 文字列として管理します。
HELLO SHELL WORLD が位置パラメータに入っているとすると $*は、 "HELLO SHELL WORLD"として管理している。 $@は、 "HELLO" "SHELL" "WORLD"のようにスペース区切りで管理されている。
環境変数(environment variables)
環境変数は、各ユーザごとに設定されている変数のことです。あらかじめ設定されている変数もあり、また自分で新しく作成することも可能です。
環境変数の一覧を表示するにはexportコマンドを使います。一覧表示されるので試してみてください。
以下のようにexport 環境変数名=値とすることで、新たに作成することも可能です。
コマンドラインでテストした例
-bash-3.00$ export ENV_PATH=/tmp
-bash-3.00$ export | grep $ENV_PATH
declare -x ENV_PATH="/tmp"
シェルスクリプトでも同じように記述できる
#!/bin/sh
export ENV_PATH=/tmp
export | grep $ENV_PATH
変数と環境変数の違い
違いは変数を引き継ぐかどうかの違いです。以下はparent.shからchild.shを呼び出すサンプルです。
parent.shのサンプル
#!/bin/sh echo "parent.sh開始 ----------" variable="shell data" # 変数の定義 export environment_variable="environment data" # 環境変数の定義 sh child.sh # シェルスクリプト内からchild.shを呼ぶ。 # 表示 echo "変数は、$variable" echo "環境変数は、 $environment_variable" echo "parent.sh終了 ----------"
child.shのサンプル
#!/bin/sh echo "child.sh開始 **********" echo "変数は、$variable" echo "環境変数は、 $environment_variable" echo "child.sh終了 **********"
実行した例
-bash-3.00$ sh parent.sh parent.sh開始 ---------- child.sh開始 ********** 変数は、 環境変数は、 environment data child.sh終了 ********** 変数は、shell data 環境変数は、 environment data parent.sh終了 ----------
実行した例の3行目でchild.shが実行されています。これはちょうど、parent.shで"sh child.sh"と 実行している部分です。その後変数と環境変数の表示をしていますが、環境変数のみ表示されて child.shが終了されていると思います。
このように、子のシェルに引き継ぐのが環境変数です。
exportコマンドの互換性を意識する
LinuxやMac OSXは標準のシェルがbashなので問題ないですが、SolarisやHP-UXなどのUnixの場合上記の exportは正常に動作しません。そのため一度シェル変数として定義してexportコマンドを実行することで互換性の高いシェルスクリプトを書くことが出来ます。
一般的なexportの書き方
#!/bin/sh export TEST_VAL="HOGEHOGE"; # これだとSolarisで上手く動作しない。 echo "HELLO"
互換性を意識したexportの書き方
#!/bin/sh TEST_VAL="HOGEHOGE"; export TEST_VAL # TEST_VALをはじめに定義して、その後exportコマンドを使う。 echo "HELLO"
見やすいようにTEST_VALシェル変数とexportコマンドを1行に書いて、セミコロンで文を分割していますが、2行にしても同じです。
#!/bin/sh TEST_VAL="HOGEHOGE" # TEST_VALをはじめに定義 export TEST_VAL # その後exportコマンドを使う。 echo "HELLO"
bashは、/bin/shとして起動すると、なるべくshの互換性を意識して動作します。また--posixオプションを指定することでPOSIX準拠の動作にさせることも可能です。
変数置換
シェルスクリプトには便利な変数置換という機能があります。もし変数置換機能がなかったら以下の例のように、その状況に応じてif文を記述する必要があります。変数に対する簡単な分岐処理を1行で書くことが出来るのでいろいろな場所で使われます。でも逆に言うと、変数置換を使わなくてもif文で同じ処理が書けます。
if [ 変数がnullだったら]; then
# 値を入れる。
fi
変数置換の書き方は、セミコロンの後に、-,+,=,?のいづれかを書きます。また$var:-"aaaa"ように 記述すると変数置換として認識できないので、${var:-"aaaa"}のように{}で括ります。
param.sh
#!/bin/sh
var1='start'
echo ${var1}
echo 'var1がnullの時:-を設定'
var1=
echo ${var1:-"bbb"}
echo 'var1がnot nullの時:+を設定'
var1="aaa"
echo ${var1:+"ccc"}
echo 'nullの時:=を設定'
var1=
echo ${var1:="ddd"}
echo 'var1がnullの時:?を表示'
var1=
${var1:?'empty'}
echo 'これは実行されない'
echo "end";
メタキャラクタ
シェルスクリプトには簡単にパターンマッチさせる機能も付いています。以下にサンプルを 書きましたので実際に実行してみてください。
URLなどのように高度なパターンマッチをするときは、grepを活用しますが、単純なものは これで解決できます。
meta.sh
#!/bin/sh -x # メタキャラクタ touch abc.dat touch 123.dat touch z.dat echo '*は任意の0文字以上にmatch' ls *.dat echo '?は任意の1文字にmatch' ls ???.dat echo '[]は指定した文字にmatch' ls [1]* ls [1-9]* ls [a-z]* ls [a][b][c].dat echo '[!xxx]は否定' ls [!a-z]* rm -f abc.dat rm -f 123.dat rm -f z.dat
[a-z]や[1-9]などは、[:lower:]または[:digit:]というように書き換えることも可能です。この書き方を文字クラスと呼びます。
制御構造
分岐
if文の構造
if [条件]
then
処理
elif 条件2
処理2
elif 条件3
処理3
else
処理4
fi
もしif文とthenを同じ行に書く場合は、セミコロンを間に入れないといけません。
if [条件]; then
処理
elif 条件2
処理2
elif 条件3
処理3
else
処理4
fi
簡単なサンプル
はじめのif文には、 -d /tmpと書いています。これは/tmpがディレクトリかどうかのチェックです。 次のelifでは、-f /dev/null で/dev/nullがファイルであるかチェックしています。
#!/bin/sh if [ -d /tmp ] then echo "/tmp is directory" elif [ -f /dev/null ] echo " /dev/null is file" elif [ -f /etc/passwd ] echo "password file" else echo "other process" fi
様々な分岐処理条件
test_if.sh
#!/bin/sh
echo '条件の評価 一度はman testを実行すべし'
# test [ condition ]
# [ [ condition ] ]
# if [ condition ]; then : fi
#
FILE='test.dat'
FILE_LN="$FILE.link"
touch $FILE
ln -s $FILE $FILE_LN
echo "ファイルの比較"
if [ -f "$FILE" ]
then
echo "$FILE exist and normal file"
fi
if [ -c "$FILE" ]
then
echo "$FILE exist and character special file"
fi
# -a -e はsolaris9のbourne shellでは使えない
if [ -g "$FILE" ]
then
echo "$FILE exist and set 'setgroupid'."
fi
if [ -h $FILE_LN ]
then
echo "$FILE exist and synbolic link."
fi
if [ -L $FILE_LN ]
then
echo "$FILE exist and synbolic link."
fi
if [ -p "$FILE" ]
then
echo "$FILE is pipe"
fi
if [ -r "$FILE" ]
then
echo "$FILE exist and it can read."
fi
if [ ! -s "$FILE" ]
then
echo "$FILE exist and empty."
fi
if [ -u "$FILE" ]
then
echo "$FILE exist and set 'setuserid'"
fi
if [ -w "$FILE" ]
then
echo "$FILE exist and it can write."
fi
if [ -x "$FILE" ]
then
echo "$FILE exist and it can exec."
fi
echo "文字列の比較"
if [ ! -n "$FILE" ]
then
echo "String length is not zero."
fi
if [ -z "" ]
then
echo "String length is zero."
fi
if [ "1" = "1" ]
then
echo "1=1"
fi
if [ "1" != "0" ]
then
echo "1=0"
fi
echo "整数の比較"
if [ '1' -eq '1' ]
then
echo "1 EQual 1"
fi
if [ '1' -ne '0' ]
then
echo "1 Not Equal 0"
fi
if [ '2' -ge '1' ]
then
echo "2 Greate than Equal 1"
fi
if [ '2' -gt '1' ]
then
echo "2 Greate Than 1"
fi
if [ '1' -le '2' ]
then
echo "1 Lettle then Equal 2"
fi
if [ '1' -lt '2' ]
then
echo "1 Lettle Then 2"
fi
echo "&&"
test -L "$FILE_LN" && rm -f $FILE_LN
echo "||"
test -d "$FILE" || rm -f $FILE
echo '条件の評価 一度はman testを実行すべし'
Case文
ある数値に対する多重分岐をするときには、case文を使うことが出来ます。
case 条件 in
パターン1) コマンド;;
パターン2) コマンド;;
default) コマンド;;
esac
case文はコマンドのあとに2つのセミコロンが必須です。
#!/bin/sh VAR=100 case $VAR in 50) echo "50";; 100) echo "100";; default) echo "$VAR";; esac
反復処理
for エレメント [ in list ]
do
処理
done
``は中に書かれたコマンドを実行するものです。ここではcatコマンドを実行しています。そのためfor文を実行するとごとに/etc/passwdファイルに入っている1行が取得でき、echo $ITEMの部分で表示されます。
#!/bin/sh for ITEM in ` cat /etc/passwd` do echo $ITEM done
while 条件
do
処理
done
#!/bin/sh cnt=0 while [ cnt -eq 100 ] do cnt=`expr $cnt + 1` echo $cnt done
リダイレクトとパイプ
パイプ
パイプ(|)はコマンドを繋げて書くことができます。
$ cat /etc/passwd | grep 'root' root:x:0:1:Super-User:/:/sbin/sh
cat /etc/passwdを実行した後、通常は標準出力に結果が表示されていきますが、パイプをつかっているため次のコマンドに出力が渡されます。次のコマンド(ここではgrep)はユーザのキーボード入力などではなく、このパイプの出力を入力としてコマンドを実行します。
リダイレクト
リダイレクト(>,<,>> ...)は入出力を受け渡すのに使用します。通常、入力は、キーボードから受け付けて、出力は画面に表示させます。ですが、プログラミングの場合は、コンピュータが自動入力を行って、その結果をログファイルに出力するなどの状況が想定されます。このような場合、リダイレクトを使って、入力元の指定や出力先の指定を切り替えることが出来ます。
>は、新規にtest.datファイルに出力します。
$ echo "hoge" > test.dat $ cat test.dat hoge
>は、追記書き込みでtest.datファイルに出力します。
$ echo "fuga" >> test.dat $ cat test.dat hoge fuga
<でtailコマンドの入力をtest.datにしています。
$ tail < test.dat hoge fuga
標準出力
1>で標準出力をtest.datに出力します。1>>で追記出力が可能です。
$ echo "aaa" 1> test.dat $ cat test.dat aaa $ echo "bbb" 1>> test.dat $ cat test.dat aaa bbb
標準エラー出力
2>で標準エラー出力をerror.datに出力します。2>>で追記出力が可能です。
$ cp 2> error.dat $ cat error.dat cp: missing file arguments Try `cp --help' for more information. $ mv 2>> error.dat $ cat error.dat cp: missing file arguments Try `cp --help' for more information. mv: missing file argument Try `mv --help' for more information.
通常の出力は標準出力にして、エラーの場合、標準エラー出力に書き込むことを同時に行えます。
$ cp aaa 1> out.dat 2> out.dat $ cat out.dat cp: missing destination file Try `cp --help' for more information.
標準エラー出力を標準出力と同じ場合にするには、2>&1で書くことが出来ます。
$ cp aaa 1> out.dat 2>&1 $ cat out.dat cp: missing destination file Try `cp --help' for more information.
データ処理
数値計算
通常のプログラミング言語は1 + 1と式を書けば計算してくれますが、シェルスクリプトは基本的に文字列として扱うために計算するときはexprコマンドを使います。
#!/bin/sh
CNT=1
while [ $CNT -le 10 ]
do
echo $CNT
CNT=`expr $CNT + 1`
done
eval 再帰的な実行
スクリプト言語には非常に強力な再帰処理機能があります。まずは下の例を見てください。
#!/bin/sh VAL=COFFEE Message='Java is $VAL' echo $Message eval echo $Message
まずはじめに、VAL変数に文字列COFFEEを格納しています。 次にMessage変数に文字列'Java is $VAL'を格納しています。これはシングルクォートで囲っているので VAL変数は展開されません。そのため次のecho $Messageを実行したときは、'Java is $VAL'が表示されます。
その後、evalコマンドを実行することでシングルクォートの文字列を変数展開をして第一引数をコマンドとして実行することが出来ます。
実行例
-bash-3.00$ sh ddd.sh Java is $VAL Java is COFFEE
evalコマンドは非常に強力なコマンドです。このサンプルでは単純に変数が展開できる程度にしか感じないかもしれませんが、シェルスクリプト上で作成した文字列をプログラムとして実行できるのです。つまり、状況に合わせてスクリプトを作るようなシェルスクリプトを、開発者が作るのが容易に作れるのです。このような機能は関数型言語やメタプログラミングという技法でよく扱われるものです。今はevalコマンドが存在する程度でよいですが、興味があるかたは色々調べてみてください。
ヒアドキュメント
<< 'NAME'を書くことで数行を入力にすることが出来ます。'NAME'はどんな名前でもよいです。この機能をヒアドキュメントと呼びます。
簡単なサンプル
#!/bin/sh
cat << HEREDOC
ヒアドキュメントを使うことで
複数行のメッセージや
複数行のコマンド入力などを
書くことができます。
HEREDOC
hello_value="Hello World"
cat << HOGEHOGE
ヒアドキュメント内で変数を参照することも当然出来ます。
hello_value変数は${hello_value}です。
HOGEHOGE
実行例
-bash-3.00$ sh heredoc.sh ヒアドキュメントを使うことで 複数行のメッセージや 複数行のコマンド入力などを 書くことができます。 ヒアドキュメント内で変数を参照することも当然出来ます。 hello_value変数はHello Worldです。
ftpコマンドをヒアドキュメントで自動化
今度は応用した例です。一般にftpコマンドはユーザがコマンドの入力しながら対話的に使います。けれどオプションの指定によっては、自動で実行できるように出来ます。この例では、ヒアドキュメントを使ってftpコマンドを実行したときにプロンプトを表示せずに、単純にログインした後にステータスを表示してログアウトしています。
これを改良してOSに定期的に実行させるようにすれば、データのアップロードの自動化などが単純に出来てしまいます。
#!/bin/sh # ヒアドキュメントを利用して、ftpアクセスする。 ftp -in << HEREDOC prompt off open 192.168.11.8 user okita okita status quit HEREDOC
実行例
[s-okita@stoc shell]$ sh here_doc.sh Interactive mode on. Connected to 192.168.11.8. No proxy connection. Mode: stream; Type: ascii; Form: non-print; Structure: file Verbose: off; Bell: off; Prompting: on; Globbing: on Store unique: off; Receive unique: off Case: off; CR stripping: on Ntrans: off Nmap: off Hash mark printing: off; Use of PORT cmds: on Tick counter printing: off
関数
シェルスクリプトは、普通のプログラミング言語と同じなので自分で関数を作ることが出来ます。ここではもっとも簡単な関数の例を記述しておきます。
関数名 () { 処理 }
#!/bin/sh
test_func ()
{
echo "call test_func"
return 0
}
#呼び出す
test_func
戻り値の取得
戻り値は組み込み変数の$?を利用します。echo $?を実行することで表示したり、次の分岐などに直接記述します。
#!/bin/sh
test_func ()
{
echo "call test_func"
return 5
}
#呼び出す
test_func
echo $?
-bash-3.00$ sh test_f.sh call test_func 5
引数
引数には、$1,$2,$3のように組み込み変数を利用することで取得することが可能です。
#!/bin/sh
test_func ()
{
echo "引数の値は、${1}"
return 5
}
#呼び出す
test_func "hello"
echo $?
シグナルのトラップ
OSに異常が発生した場合、OSはシグナル(信号)を発生させます。「火事だ~」、「泥棒だ~」と いろいろな状況に応じて警告をしてくれます。自分でもkillコマンドでシグナルを発生させることが出来ます。
シェルスクリプトにシグナルが発生した場合には、どうするかを記述しておけば、その状況に応じて プログラムを強制終了したり、バックアップさせたりと対応できます。
このシグナルを補足する方法はtrapコマンドを利用します。 ただひとつだけ例外があってKILL(9)のみ補足することが出来ません。
#!/bin/sh
ECHO=`which echo`
echo "trap test shellscript"
signal_handle ()
{
$ECHO "signal interupt"
}
# シグナル2,9が発生したときに、signal_handle関数を実行する
trap signal_handle 2
trap signal_handle 9
$ECHO "signal handling list"
trap
sleep 10000
test_s.shという名前で保存し実行してみます。trapコマンドにより、シグナル2のINTとシグナル9 のkill時に補足できる状態になりました。このとき別のコンソールからkillコマンドでシグナルを送ってみたいと思います。
コンソール1:test_s.shを実行
-bash-3.00$ sh test_s.sh trap test shellscript signal handling list trap -- 'signal_handle' INT trap -- 'signal_handle' KILL
コンソール2:killコマンドでシグナル2を発生させてみる
-bash-3.00$ ps PID TT STAT TIME COMMAND 8995 p0 Ss 0:00.50 -bash 9267 p0 S 0:00.05 sleep 10000 9273 p0 S+ 0:00.16 sh test_s.sh 532 p2 S 0:00.05 -bash 837 p3 S+ 0:00.26 -bash 9238 std Ss 0:00.20 -bash -bash-3.00$ kill -2 9276
sh test_s.shがプロセス番号が9273として動いています。このシェルスクリプトはsleep状態にしているので、プロセス番号9267にシグナル2を送ります。そうすると以下のようにコンソール1では、test_s.shシェルスクリプトがシグナルを補足して停止します。
-bash-3.00$ sh test_s.sh trap test shellscript signal handling list trap -- 'signal_handle' INT trap -- 'signal_handle' KILL signal interupt #シグナル2を送ったのでsignal_handle関数が実行され表示された。
シグナル9のkillを送った場合は、シグナルが補足されないためsignal_handle関数は呼ばれません。実際に実行して試してみてください。
API(Application Program Interface)
シェルスクリプトは、組み込みコマンドとそのプラットフォームで使えるコマンドが全てAPIとなります。そのためPerlやRubyなどで出来ることはシェルスクリプトでもほとんど実現が可能です。はじめは文法やコマンドを覚えるのが大変ですが、ある程度コマンド使えるようになると、それが全てプログラミングとして使えるので、データ変換やテストデータを作成する時など非常に重宝します。
組み込みコマンド
findやgrepやwhichなど一般の外部コマンドと呼ばれます。これらはOSについているものでsh自体に付いているコマンドではありません。ではsh自体についているコマンドとは何でしょうか?それを確認するには、コマンドラインでhelpを実行します。helpは組み込みコマンドでシェルに組み込まれているコマンドの説明を表示してくれます。
また、whichコマンドでコマンドの場所が探せる場合は外部コマンドと考えてもよいと思います。そして、builtinコマンドのmanページからも組み込みコマンドであるかどうかを確認できます。
-bash-3.00$ help
GNU bash, version 3.00.0(1)-release (powerpc-apple-darwin7.9.0)
These shell commands are defined internally. Type `help' to see this list.
Type `help name' to find out more about the function `name'.
Use `info bash' to find out more about the shell in general.
Use `man -k' or `info' to find out more about commands not in this list.
A star (*) next to a name means that the command is disabled.
%[DIGITS | WORD] [&] (( expression ))
. filename [arguments] :
[ arg... ] [[ expression ]]
alias [-p] [name[=value] ... ] bg [job_spec]
bind [-lpvsPVS] [-m keymap] [-f fi break [n]
builtin [shell-builtin [arg ...]] caller [EXPR]
case WORD in [PATTERN [| PATTERN]. cd [-L|-P] [dir]
command [-pVv] command [arg ...] compgen [-abcdefgjksuv] [-o option
complete [-abcdefgjksuv] [-pr] [-o continue [n]
declare [-afFirtx] [-p] [name[=val dirs [-clpv] [+N] [-N]
disown [-h] [-ar] [jobspec ...] echo [-neE] [arg ...]
enable [-pnds] [-a] [-f filename] eval [arg ...]
exec [-cl] [-a name] file [redirec exit [n]
export [-nf] [name[=value] ...] or false
fc [-e ename] [-nlr] [first] [last fg [job_spec]
for NAME [in WORDS ... ;] do COMMA for (( exp1; exp2; exp3 )); do COM
function NAME { COMMANDS ; } or NA getopts optstring name [arg]
hash [-lr] [-p pathname] [-dt] [na help [-s] [pattern ...]
history [-c] [-d offset] [n] or hi if COMMANDS; then COMMANDS; [ elif
jobs [-lnprs] [jobspec ...] or job kill [-s sigspec | -n signum | -si
let arg [arg ...] local name[=value] ...
logout popd [+N | -N] [-n]
printf format [arguments] pushd [dir | +N | -N] [-n]
pwd [-PL] read [-ers] [-u fd] [-t timeout] [
readonly [-af] [name[=value] ...] return [n]
select NAME [in WORDS ... ;] do CO set [--abefhkmnptuvxBCHP] [-o opti
shift [n] shopt [-pqsu] [-o long-option] opt
source filename [arguments] suspend [-f]
test [expr] time [-p] PIPELINE
times trap [-lp] [[arg] signal_spec ...]
true type [-afptP] name [name ...]
typeset [-afFirtx] [-p] name[=valu ulimit [-SHacdflmnpstuv] [limit]
umask [-p] [-S] [mode] unalias [-a] name [name ...]
unset [-f] [-v] [name ...] until COMMANDS; do COMMANDS; done
variables - Some variable names an wait [n]
while COMMANDS; do COMMANDS; done { COMMANDS ; }
デバック
xオプション
shコマンドには、-xオプションをつけることが出来ます。-xオプションをつけるとインタプリタが一行一行読み込んで実行した事を標準出力に出力するのでデバックが容易に出来ます。
sh -x test.sh
#!/bin/sh -x # -xオプションでデバックモードにしている cp src.txt /tmp/dist.txt echo "cp test ok."
timeコマンド
またtimeコマンドを使うと実行時間を簡単に取得できます。これにより全体でどのぐらい掛かるのかが測定できます。
#!/bin/sh echo "time command test."
-bash-3.00$ time sh test.sh time command test. real 0m0.260s user 0m0.120s sys 0m0.020s -bash-3.00$ cat test.sh
実践
実際の開発では、シェルスクリプトファイルに直接コマンドを記述しないことが多いようです。これは、セキュリティ対策とプラットフォーム依存を防ぐためです。例えば、SolarisとLinuxではコマンドが配置されている場所が異なります。以下のシェルスクリプトはsolaris9で動作できます。(/usr/bin/echo)。しかしLinuxではECHOコマンドは/bin/echoにあります。これをすべてのスクリプトファイルに直接コーディングするとメンテナンス性が悪いプログラムが出来てしまいます。
互換性のない例
以下は、/usr/bin/echoコマンドを利用したシェルスクリプトの例です。この場合echoコマンドが/binディレクトリのみの場合正常に動作しません。
#!/bin/sh
/usr/bin/echo "test mesage"
互換性のを保つ例
互換性を保つよい例としては、whichコマンドを使う方法があります。以下のようにはじめにwhichコマンドで変数にechoコマンドの場所を読み込んで、変数から実行させます。
#!/bin/sh
ECHO_COMMAND=`which echo`
$ECHO_COMMAND "test mesage"
互換性のを保つ例を応用する
互換性のあるちょっとしたシェルスクリプトを書く場合は、上記の例で十分ですが、ソフトウェア開発では運用のためのシェルスクリプトをかなりの量書く場合があります。こういう場合は、C言語のinclude文やJava言語のimport文のように、ファイルのはじめの部分でコマンドを読み込ませる方法などがよく使われます。
config.sh
#!/bin/sh ECHO=/usr/bin/echo CP=/usr/bin/cp MV=/usr/bin/mv
test_start.sh
#!/bin/sh #設定ファイルの読み込み . config.sh ECHO "test mesage"
計算処理の問題点
シェルスクリプトなどインタプリタ言語は、コンパイルされていないのでインタプリタが一行づつ読み込んで処理をするため、ちょっとした修正で大きなパフォーマンスの変化が得られます。
以下のプログラムは、Celeron300Mhz,Memory640MBの環境で実行途中にエラーが発生したサンプルです。実際に実行してみると分かるのですが、whileループが進むごとに処理が重くなっていくのが分かります。
#!/usr/bin/sh
CNT=0
MAX_SIZE=65536
TMP_DATA="0123456789ABCDEF"
OUT_DATA=""
while [ ${CNT} -lt ${MAX_SIZE} ]; do
CNT=`expr ${CNT} + 1`
OUT_DATA="${TMP_DATA}${OUT_DATA}"
echo ${CNT}
done
echo OUT_DATA
上記、シェルスクリプトを実行すると”スワップ領域不足です。”と警告をうけスクリプトが停止してしまいました。変数OUT_DATAにデータを貯めていくものですが大きなデータを演算することが出来ないようです。メモリ上でデータを保持するのではなくこまめにファイル出力することで問題解決する方法を記述します。
#!/usr/bin/sh
CNT=0
MAX_SIZE=65536
TMP_DATA="0123456789ABCDEF"
OUT_FILE="test.dat"
while [ ${CNT} -lt ${MAX_SIZE} ]; do
CNT=`expr ${CNT} + 1`
/usr/bin/echo "${TMP_DATA}\c" >> ${OUT_FILE}
echo ${CNT}
done
cat ${OUT_FILE}
