S-JIS[2006-06-27/2014-09-27]

UNIXシェルスクリプト

UNIXのコマンドをファイルに書いておき、バッチとして実行することが出来る。(MS-DOSのバッチファイルに相当)

ただ単にコマンドを羅列するだけでなく、スクリプト言語を使ってプログラミングできる。
このスクリプト言語は、シェルの種類によって微妙に(大幅に?)異なる。


シェルスクリプトの実行方法

sh スクリプトファイル
ボーンシェルのスクリプトを実行する。
bash スクリプトファイル
ボーンアゲインシェルのスクリプトを実行する。
ksh スクリプトファイル
kshのスクリプトを実行する。

-xを付けると実行内容を表示しながら実行する。[2008-11-29]

bash -x スクリプトファイル

-nを付けると、実行せずにファイル内の構文解析(エラーの有無のチェック)を行う。[2008-11-29]

bash -n スクリプトファイル

. スクリプトファイル

現在実行中のシェルと同じシェルを使ってスクリプトを実行する。
スクリプトの中で環境変数を設定した場合、現在のシェルの環境にも影響する (スクリプトの中で定義した環境変数が実行元でも有効になる)。

ファイルに実行権限(chmod +x)が付いている場合は、以下のようにして実行できる(相対パスで指定している)。

./スクリプトファイル

ファイルに実行権限(chmod +x)が付いており、かつ環境変数PATHにカレントディレクトリ「.」が含まれている場合は、以下のようにして実行できる。

スクリプトファイル

シェルスクリプトの書き方

ファイルの先頭で、どのシェルで実行するかを宣言する。(→「#!」の意味

#!/bin/sh		←「#!」の後に、シェルの実行コマンド名をフルパスで記述

宣言したシェルには、シェルの引数を付けることも可能。

#!/bin/sh -x

ただし、これらの宣言は、シェルスクリプトに実行権限を付けて直接実行した場合のみ有効。
shや「.」を使って(ファイルを引数として)実行した場合は、そのシェルとして実行される。(その結果、使えない文法があれば実行時エラーとなる)

また、(ただ単にコマンドを羅列するだけのような)どのシェルで書いても同じものについては、特に宣言しなくても動く。

その他の場所では、「#」から始まる行はコメントとして無視される。(MS-DOSではremコマンドでコメント)
MS-DOSでは:」で始めるとラベルになるのでコメント代わりに使われることもあるようだが、bashでは「:」は何もしない命令(戻り値は常に0)である。[2014-09-27]
(何もしないと言っても、引数の展開やリダイレクトは実行される)

シェルの中では、改行コードは(通常のUNIXの)「0a」しか使えない。
(MS-DOSでの改行コードは「0d0a」だが、シェルスクリプトのファイルがこれだと うまく実行されない)


シェルスクリプトファイルの場所の取得方法

実行しているシェルスクリプトファイルの場所は、以下のようにして取得できる。[2011-10-26]

BASEDIR=$(cd $(dirname $0);pwd)

$0は実行中のシェルスクリプトのファイル名。
dirnameを使うことで、シェルスクリプトのディレクトリーが取得できる。
ただしこれは相対パスかもしれないので、cdでそのディレクトリーに移動し、pwdでその場所(絶対パス)を取得している。

MS-DOSでバッチファイルの場所を取得する方法


変数の使い方

シェルスクリプト内では、環境変数を文字列の変数として扱える。(数値として扱うには特殊な方法が必要)
変数名には、大文字小文字の区別がある。

変数名=値」で代入できる。
代入を表す「=」の前後には、スペースを入れてはならない

ABC=hoge
ABC = hoge	←エラーになる

変数を削除するにはunsetを使う。

変数を使うには、変数名の先頭に「$」を付ける。また、${」〜「}でくくっても使える。

echo $ABC
echo ${ABC}
echo "$ABC"
ZZZ="$FOO$HOGE"	←$FOOと$HOGEに入っている文字列の結合
echo '$ABCの値は' "$ABCです"

ダブルクォーテーション"」で文字列をくくると、その中にある変数は展開される。
シングルクォーテーション'」で文字列をくくると、文字列定数として扱われる。

変数名+=値」で変数に値を追加できる。「変数名=${変数名}値」と同じ。[2011-07-23]


bashの場合、「$'文字列'」で(C言語風の)エスケープ文字が使える。[2008-11-29]

$ echo $'abc\ndef'
abc
def

また、プレース展開という機能もある。{ }内に文字列をカンマ区切りで並べると、それを展開してくれる。

$ echo abc{123,456,789}def
abc123def abc456def abc789def

シェルスクリプト標準では変数を数値として扱えないので、演算するコマンドを使う。

I=0
I=`expr $I + 1`

バッククォート`」で文字列をくくると その中をコマンドとして実行し、そのコマンドが標準出力に出力した内容が結果となる。
すなわち、数値演算を行うexprを利用して変数を数値の様に扱う。


kshやbashの場合、「$(コマンド)」($+単一の丸括弧)はバッククォート指定`コマンド`」と同じ。
$()が使えるシェルなら、バッククォートよりも$()を使うべき。(バッククォートは古い形式であり、ネストできないし、開始と終了もまぎらわしい)

また、exprの代わりに「$((演算))」($+二重の丸括弧)が使える。 この括弧の中では空白は自由に入れられる。

I=0
I=$(($I+1))
I=$(($I + 1))
I=$(( $I + 1 ))

特殊な変数

シェルの実行中に自動的に値が設定される変数がある。
引数を扱う変数にはシェルの実行時引数が入っているが、関数が呼び出された場合にはその関数の引数が入る。

MS-DOSとは異なり、引数を指定する際にダブルクォーテーションが付いていたら、そのダブルクォーテーションは削除される。(だから$@のような指定があるわけ)

変数 内容 MS-DOS相当
$? 直前に実行されたコマンド(や関数)の戻り値(→PIPESTATUS %ERRORLEVEL%
$0 実行コマンド名 %0
$1 $2 … 実行時引数(個別) %1 %2 …
$* 全引数 %*
$@ 全引数(それぞれの引数がダブルクォーテーションで囲まれる)
$# 引数の個数  
$$ 当シェルのプロセスID  
$! 最後に呼び出されたバックグラウンドコマンドのプロセスID [2006-08-26]  
$- シェルのオプション(フラグ)の内容。[2008-11-29]  
$_ シェルスクリプト内で使った場合、起動されたシェルのファイル名。$0と同じ。[2008-11-29]
それ以外の場合、直前に実行したコマンドの最後の引数。(引数が無かった場合はコマンド名)
 
$PIPESTATUS パイプの各コマンドの戻り値が入った配列。[2012-03-04]  
$LINENO 現在の行番号。[2012-03-04]  
$IFS 区切り文字。(→使用例[2014-05-01]  

0,1,2や*,@は位置パラメータと呼ぶらしい。[2008-11-29]

shiftコマンドで、引数の並びが1つ左へずれる。MS-DOSのshiftに相当。
すなわち、$1には$2の値が入り、$*,$@は先頭(一番左。$1の分)が削除される。$#の値も1減る。
$0は変わらない)


変数の値を加工して取得することが出来る。

指定方法 説明 対応シェル
${NAME:-デフォルト値} $NAMEが未定義か空文字列のとき、デフォルト値になる。 NAME=""
echo ${NAME:-"DEFAULT"}

→「DEFAULT」が表示される
sh ksh bash
${NAME-デフォルト値} $NAMEが未定義のとき、デフォルト値になる。   sh ksh
${NAME%接尾辞} $NAMEから一致する最小部分の接尾辞を削除したものになる。 x=posix/src/std
echo ${x%/*}

→「posix/src」が表示される
ksh bash
${NAME%%接尾辞} $NAMEから一致する最大部分の接尾辞を削除したものになる。 x=posix/src/std
echo ${x%%/*}

→「posix」が表示される
ksh bash
${NAME#接頭辞} $NAMEから一致する最小部分の接頭辞を削除したものになる。   ksh bash
${NAME##接頭辞} $NAMEから一致する最大部分の接頭辞を削除したものになる。   ksh bash
${#NAME} $NAMEの文字数(全角文字も1文字)になる。 echo ${#1}
→$1の文字数が表示される
ksh bash

配列

bashでは配列を扱うことも出来る。[2008-11-29]

配列への代入

名前の後ろに[n]を付けると個別の配列に代入することが出来る。(添字は0以上)

NAME[0]=value

複数の添字にまとめて代入(複合代入)することも出来る。(スペース区切り)

NAME=(value1 value2 …)

配列の内容の取得

以下のようにして配列の内容を取得する。

指定方法 説明
${NAME[添字]} 指定された要素の内容。
${NAME} ${NAME[0]}と同じ。
${NAME[*]}
${NAME[@]}
配列内の全要素。
${#NAME[添字]} 指定された要素の文字数。
${#NAME} ${#NAME[0]}と同じ。
${#NAME[*]}
${#NAME[@]}
配列の要素数。

ティルダ展開

UNIXでは、ティルダ「~」1文字だと現在のユーザーのホームディレクトリーを表す。[2008-11-29]

$ echo ~
/home/hishidama

「~ユーザー」で、そのユーザーのホームディレクトリーを表す。


bashでは、ティルダを使って、移動してきたディレクトリーを指定できる。

指定 説明
~+ 現在のカレントディレクトリー。$PWDと同等。
$ echo ~+
/home
~- 1つ前のカレントディレクトリー。$OLDPWDと同等。
$ echo ~-
/usr
~数値 pushdで保持されているディレクトリー。
0でカレント、1で1つ前、2で2つ前…
$ dirs
/home /usr /bin
$ echo ~2
/bin

シェルスクリプトの構文

if

if コマンド
then
 〜
fi
if コマンド
then
 〜
else
 〜
fi
if コマンド
then
 〜
elif コマンド
then
 〜
else
 〜
fi
if コマンド; then 〜; else 〜; fi

MS-DOSのifに相当。
コマンドの実行結果(戻り値)が正常(0)の場合、then部を実行する。エラー(0以外)の場合、else部を実行する。
(したがって、ほとんどのコンピュータ言語と異なり、「0が真/0以外が偽」になっていると言える)
ifで始まりfiで終わるので、if文をネストさせることも出来る。

if rm zzz.txt
then
  echo "remove success"
else
  echo "remove fail"
fi

変数の内容を比較したりする場合は、testコマンドを使用する。

if test "$ABC" = "hoge"
then
  echo '$ABCの値はhogeです'
else
  echo '$ABCの値はhogeじゃない'
fi

また、testの条件判断はよく使われるので、代替表現[ ]←角括弧」がある(シェルの組み込み命令)。

if [ "$ABC" = "hoge" ]
then
  echo '$ABCの値はhogeです'
else
  echo '$ABCの値はhogeじゃない'
fi

ifと[の間には空白が必要。角括弧と式の間にも空白が必要。

ここで使える演算子は「man test」で確認することが出来るが、おおよそは以下の通り。

比較演算子
= != 文字列比較 kshの場合、「=」は単純な比較ではなく
パターン(正規表現風)との比較になる。
-eq -ne 整数比較  
-lt -gt
-le -ge
整数大小比較  
単項演算子
! 否定
-d ディレクトリとして存在していれば真
-f 通常のファイルとして存在していれば真
-h シンボリックリンクとして存在していれば真
-r 読取可能ファイルとして存在していれば真
-w 書込可能ファイルとして存在していれば真
-x 実行可能ファイルとして存在していれば真
-z 文字列の長さが0であれば真

また、「-a」でand、「-o」でor条件を表すことが出来る。

丸括弧を使って優先順位を変えることも出来るが、丸括弧自体がシェルで別の意味を持つので、使用するには引用符が必要。
\(\)」「"("")"

&&||を使うこともできるが、これはコマンドを並べて実行するものであり、testに対する引数ではない。 すなわち、角括弧内で使うことはできない。
&&,||は混在していても左から右へ実行され、優先順位に違いがない)

if [ "$ABC" = "foo" -o "$ABC" = "zzz" ]; then 〜; fi
if [ "$ABC" = "foo" ] || [ "$ABC" = "zzz" ]; then 〜; fi

ifで比較する際、先頭に「x」とかの一文字を付けて比較しているのを見たことがある。[2012-03-04]

if [ x"$ABC" = x"foo" ] 〜

これは、([]の実体である)testコマンドのオプションや演算子が入ってきてもちゃんと動作させる為らしい。
つまり、「"$ABC" = "foo"」だと、$ABCが「-f」だったら「-f = foo」になるが、エラーになってしまうのだとか。bashだと大丈夫そうだが…。

参考: Shintaさんの変数がNULLかわからないので


kshのif

kshの場合、一重の角括弧以外にも条件表記方法が用意されている。

(( 演算 ))

数値での比較演算を行う。それぞれの項がダブルクォーテーションで囲ったのと同じ扱いになるので、「<」などの演算子もそのまま使える。(普通はリダイレクションとして扱われてしまう 。exprの注意を参照)

if (( $# < 1 ))
then
 〜
fi

[[ 演算 ]]

文字列を比較したりファイル属性をチェックしたりするのに使う。
この二重角括弧内では、ファイル名生成(ワイルドカードの展開)が実行されない。
また、「&&」といった演算子も論理演算として使える。(ふつうの角括弧内では使えない

kshの“=演算”は、正規表現風の判断となる。[2007-09-21]
通常の「if [ $1 = [0-9]* ]」という書き方は、[0-9]*ワイルドカードとして扱われて展開され、カレントディレクトリの該当ファイル(この例だと先頭が数字のファイル)に置き換えられてから実行される($1の内容がそのファイル名と同じかどうかを問うという事になる)。
一方、kshの「if [[ $1 = [0-9]* ]]」という書き方は、$1の先頭が数字かどうかを問うものとなる。

kshのこの書き方は正規表現に似ているが、正規表現ではない
*」は「任意の文字0文字以上」であって、「直前に指定した文字が0個以上」ではない。
「任意の1文字」は「?」であって、「.(ピリオド)」ではない。
+」は「直前に指定した文字が1個以上」ではなく、本当に「+」という字そのものである。
数量子「{n,m}」や「^」「$」も、それぞれの文字そのものとして扱われる。($の後に文字や数字を続けると環境変数だが)
つまりは、UNIXの普通のワイルドカードと同じ。

したがって、例えば4文字の数字は[0-9]{4}と書くことは出来ず、[0-9][0-9][0-9][0-9]と書かねばならない…。

本当に正規表現を使いたい場合は、egrep等のコマンドに頼るしか無さそう。

#!/bin/ksh
if [[ ! -z `echo "$1" | egrep "^[0-9]+$"` ]]
then
  echo 数値チェックOK
fi

while

while コマンド
do
 〜
done
while コマンド; do
 〜
done
while コマンド; do 〜; done

条件が真(コマンドの戻り値が0)の間、実行を繰り返す。

ループから抜けるには、以下の命令が使える。

continue 〔レベル〕 ループの先頭へ戻る。
break 〔レベル〕 ループを終了する。
exit 〔戻り値〕 シェルを終了する。

レベルを指定すると、その個数のループに対して実施(戻る/終了)する。(「break 2」なら、2つのループから抜ける)
省略した場合のデフォルトは1。


for each

for 変数 in 複数の値(スペース区切り)
do
 〜
done
for 変数
do
 〜
done
for 変数 in 複数の値
do 〜; done
for 変数 in 複数の値; do 〜; done

MS-DOSのforに相当。
in以降の複数の値について、1個ずつ処理を行う。

for i in aa bb cc
do
  echo i= $i
done

 ↓

i= aa
i= bb
i= cc

実行時引数を処理する場合は、in以降を省略可能。

for i		←「for i in "$@"」と同じ
do
  echo i= $i
done

上記の例で、実行時引数が「a "b c" d」という3つのとき、echoが3回実行されて「a」「b c」「d」が表示される。
"$@"」でなく「$@」や「$*」だと、「a」「b」「c」「d」の4個が表示される。
"$*"」だと、「a b c d」という1つの文字列として認識され、1個しか表示されない。

in以降は通常はスペースで区切るが、変数IFSで区切り文字を変えられる。

SAVEIFS=$IFS	←元の区切り文字を保存(保存する変数名は自分で適当に付けた)
IFS=":"		←コロン区切りに変えてみた
F="aa:bb:cc"
for i in $F
do
  echo i= $i
done
IFS=$SAVEIFS	←元の区切り文字に戻す

IFSに改行文字を指定したい場合(改行のみを区切りとしたい場合)は以下のようにする。[2014-05-01]

IFS=$'\n'

参考: linux.just4fun.bizさんのスペースが含まれる文字列を1行として扱う方法


数値を順番に扱うには、以下のように書く。MS-DOSのforに相当。[2011-10-26]

for i in {1..10}; do echo $i; done
for i in $(seq 1 10); do echo $i; done

switch

case $変数 in
 ) 〜;;
 |値…) 〜;;
 *) 〜;;
esac

変数が値に一致したとき、その部分が実行される。
|」で複数の値を指定することが出来る。
また、値の指定にはワイルドカードや正規表現を使うことも出来る。したがって、最後に「*)」を置けば、他の条件に一致しなかった場合に常に実行されることになる。

各文は「;;」で終わるので、複数のコマンドを書くことも可能。

case $1 in
 a) echo case-a;;
 "b") echo case-b
      echo case-b2
      ;;
 c|cc) echo case-c;echo case-c2;;
 d*) echo case-d;;			…dから始まるもの(ワイルドカード)
 *.[ch]) echo case ext;;		…*.c又は*.h(正規表現)
 *) echo case-e;;
esac

関数

シェルの途中で関数(サブルーチン)を定義し、呼び出すことが出来る。MS-DOSのサブルーチンに相当。

定義 呼び出し
関数名()
{
 〜
}
関数名
関数名 引数…
function 関数名()
{
 〜
}

定義した後でのみ 呼び出すことが出来る。

関数内では、引数は$*等によって使用可能(すなわちシェルの実行時引数と同様)。したがって、関数定義には引数に当たるものは何も書かない。
関数内で環境変数を使用できるし、それを変更すると呼び出し元の同名変数の値も変わる。
ただし、引数($*等)については、関数の外側のものはそのまま保持される。

変数への代入時に「local」を付けると関数内専用のローカル変数になり、それを変更しても呼び出し元には影響しない。[2014-05-01]

#!/bin/bash

function testLocal()
{
  local TEST1=123
  TEST2=456

  echo func TEST1=$TEST1
  echo func TEST2=$TEST2
}

TEST1=abc

testLocal #関数呼び出し

echo TEST1=$TEST1
echo TEST2=$TEST2

↓実行結果

func TEST1=123
func TEST2=456
TEST1=abc
TEST2=456

関数から戻るには「return 戻り値」を使用し、$?で戻り値を取得できる。
ただし、kshの場合は戻り値は下位8ビット分のみ。(shだと65540でも返ったから、2バイト分以上でも大丈夫そうだ)

#!/bin/bash

function retTest()
{
  return 123
}

retTest #関数呼び出し
echo $?

↓実行結果

123

returnが無い場合は、関数内で最後に実行したコマンドの戻り値が返る。[/2014-05-01]

ちなみにexitはシェルの終了なので、関数内で使ってもシェルが終了する。


kshの場合、他の定義方法も使える。

function 関数名
{
 〜
}

呼び出し方は普通のシェルと同様で、引数の使い方も同様。


参考


技術メモへ戻る
メールの送信先:ひしだま