TOP / Bourneシェルスクリプト入門(+bash)

Bourneシェルスクリプト入門(+bash)

目次

はじめに

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}
目次へ戻る


イバラキングへのリンク Get Firefox Valid XHTML 1.1 Apple Darwinへのリンク