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

Linux
スポンサーリンク

はじめに

こんにちはー!

日ごろシェルスクリプトを書くのですが、毎回忘れてしまいそのたびに調べるのが大変なので、情報を集約した記事を書いてみました。

間違いやすい注意文も記載することで、より理解が深まるかなと思います。

Raspbianの標準シェルで動作確認済みです。また、汎用性を高めるため、シバン(1行目に書くやつ)に#!/bin/shを記載したsh縛りとしたいと思います。

変数

指定した値を代入

以下のように変数名とイコールを書き、右側に代入したい値を記載する。

a=1
echo $a

b=01
echo $b

c="1 2 3"
echo $c

これを実行すると、以下のようになる。

1
01
1 2 3

上記から分かるように、シェルスクリプトの変数は全て文字列型である。数値を代入したつもりでも、文字列として扱われる。

ダブルクォーテーションで囲むこと

文字列を扱う場合は、ダブルクォーテーションで囲むことを心掛けるほうが良い。なぜならば、ダブルクォーテーションで囲わないと、スペースで空けたあとの文字が別の命令として処理されてエラーが発生するためである。

試しにスペースで開けた文字を代入する。

d=1 2 3
echo $d

これを実行すると、以下のように、2がないとエラーが発生する。

./test.sh: 2: ./test.sh: 2: not found

スペースを空けないこと

スクリプトを覚えたての頃によくやってしまうのだが、変数へ代入する際は、変数名とイコールと代入値との間にスペースをあけてはいけない

a=1
echo $a

b =1
echo $b

c= 1
echo $c

これを、実行すると以下のようにエラーが発生し、bcの値が表示されない。

1

./test.sh: 6: ./test.sh: b: not found

./test.sh: 9: ./test.sh: 1: not found

複数行の代入

ダブルクォーテーションで囲むことで複数行を代入することも可能である。

A="1行目
2行目
3行目"

echo "${A}"

これを実行すると、以下のようになる。

./test.sh
1行目
2行目
3行目

例えば、ブロック内を表すために以下のように左側にタブを入れてしまった場合、タブごと代入されるため注意すること。

	A="1行目
	2行目
	3行目"
	
	echo "${A}"

これを実行すると、以下のようになる。

./test.sh
1行目
        2行目
        3行目

ホームディレクトリの代入

先ほどダブルクォーテーションでくくることを伝えたが、チルダ~を利用したホームディレクトリを用いたファイルパスを変数に代入するときには、ダブルクォーテーションで囲むと問題が発生する

a=/bin/
echo $a

a="/bin/"
echo $a

a=./bin/
echo $a

a="./bin/"
echo $a

a=~/bin/
echo $a

a="~/bin/"
echo $a

これを実行すると、以下のようになる。

/bin/
/bin/
./bin/
./bin/
/home/pi/bin/
~/bin/

つまり、ダブルクォーテーションで括ると文字列として扱われるため、チルダ~がホームディレクトリとして展開されないのである。

少し難しいテクニックだが、ホームディレクトリを使用する場合は、以下のように`をくくってホームディレクトリを出力した値を利用して、それをパスに含めるようにするとよい。

a="`echo ~/`bin/"
echo $a

実行結果を変数に代入

指定したコマンドの結果を画面に表示するのではなく、変数に代入したいということがよくあると思われる。そのような場合は、そのコマンド自体をバッククォート(`)で括ることで、代入が行える。

例えば、以下を実行するとAsia/Tokyoが2回表示される。

# 直接表示
cat "/etc/timezone"

# 結果を代入してから、代入した値を表示
tz=`cat "/etc/timezone"`
echo "${tz}"

ちなみにコマンド自体をバッククォート(`)で囲うというのはよくやるテクニックであり、以下のようにif文内で直接利用することも可能である。

if [ "`cat \"/etc/timezone\"`" = "Asia/Tokyo" ];then
	echo "ok"
fi

変数から展開

変数から戻す

変数から元に戻すときは、$xのように変数名に$を付けて戻す。

# 代入して、それを表示
A="abc"
echo $A

なお、注意したいのは変数は単純に展開しているだけという認識を持つことである。つまり、C言語の#defineのようなマクロ展開をしているだけである。

そのため、以下のようにコード自体を代入して、展開させて実行させることもできる。

A="echo ""A B C D E"""
$A

"""のエスケープである。

これを実行すると、以下のようになる。

./test.sh
A B C D E

注意点

ダブルクォーテーションでくくること

これまでに話したように、あくまで展開しているということを考えると、展開された後に正しく利用されるために変数自体をまるごとダブルクォーテーションで括っておくのが大事である。

数値しか代入されないのであればいいが、半角スペースが混じった文字列が代入されている場合に、ダブルクォーテーションでくくって展開しないと問題が発生することがある。

例えば以下のように、変数に半角スペースがまじったテキストを代入し、展開時にはダブルクォーテーションでくくった場合と、くくっていない場合を比較する。

SPACE_TEST="0[] 1[ ] 2[  ] 3[   ]"

# 直接展開する
echo $SPACE_TEST

# ダブルクォーテーション内で展開する
echo "$SPACE_TEST"

これを実行すると以下のようになる。ダブルクォーテーションで括らずに展開したほうは、半角スペースが1バイト分で表示されてしまっている。

./test.sh
0[] 1[ ] 2[ ] 3[ ]
0[] 1[ ] 2[  ] 3[   ]

最初のechoはダブルクォーテーションでくくっていなかったため、echoプロセスに、対して半角スペースが区切りで、第1引数が0[] 1[、第2引数が、] 2[のように、分解して渡しているためである。

2つ目のechoはダブルクォーテーションでくくっているため、echoプロセスに、第1引数が0[] 1[ ] 2[  ] 3[   ]と分解されず、正確にわたっている。

{}でくくること

説明では、{}でくくっていないが、変数の展開は$xと書いてもいいし、{}で囲って${x}と書くことも可能である。どちらがいいかというと、{}でくくることに統一したほうが良い。以下のように見ればわかるが、文字列の結合などを考えると、{}で括ったほうがよいためである。

text="bc"

# このように書ける
echo a${text}d

# 1つの変数とみなされてしまうため書けない
echo a$textd

ミスに気が付かないまま上記を実行しても、未初期化の変数は文字数0の空白として扱われていまうため、エラーは発生せず不具合の元となる

./test.sh
abcd
a

特殊な変数

いくつか、特殊な変数が用意されている。

定数解説
$0実行時のスクリプト名。絶対パスで実行した場合は絶対パスになる。
$$自分のスクリプトのプロセスID
$*引数全て
$#引数の数
$1, $2, …引数1、引数2、…
$?直前のコマンドの数値の戻り値。0は正常終了とする。
$!直前に末尾に&を付けバッググラウンドで実行したプロセスID

例として以下のスクリプトを書いた後に、$?を表示させてみる。

#!/bin/sh

# スクリプト名
echo $0

# このスクリプトのプロセスID
echo $$

# 引数全体
echo $*

# 分解した引数
echo $1
echo $2

# 引数の数
echo $#

exit 123

実行結果は、以下の通りとなり、戻り値も$?で確認できることが分かる。

pi@raspberrypi:~/ $ ./test.sh A B
./test.sh
2692
A B
A
B
2
pi@raspberrypi:~/ $ echo $?
123

末尾に&を付けて、バッググラウンドで動作させた場合も確認する。

pi@raspberrypi:~/ $ /home/pi/Desktop/test.sh A B &
[1] 3391
/home/pi/Desktop/test.sh
3391
A B
A
B
2
[1]+  終了 123              ./test.sh A B
pi@raspberrypi:~/ $ echo $!
3391

スクリプト内部で表示させているプロセスIDと、実行後に表示した$!の値が等しいことが分かる。また、絶対パスで実行したため、1行目のスクリプト名が絶対パスになっていることも分かる。

変数内の文字列の長さ

変数${VALUE}があったときに、${#VALUE}のように変数名の先頭に#を付けることで、変数内の文字列の長さとして扱うことが出来る。

A="abcd"

echo "文字列 \"${A}\""
echo "文字列長 \"${#A}\""

これを実行すると、以下のようになる。

./test.sh
文字列 "abcd"
文字列長 "4"

変数内の文字列を部分的に切り出す

bashの場合は、${VALUE:0:1}のように切り出せるが、shでは利用できないため、cutを利用する必要がある。

A="abcd"

echo "文字列 \"${A}\""
echo "1文字目 \"`echo ${A} | cut -c 1-1`\""
echo "2から3文字目 \"`echo ${A} | cut -c 2-3`\""

これを実行すると、以下のようになる。

./test.sh
文字列 "abcd"
1文字目 "a"
2から3文字目 "bc"

計算

整数の計算

「実行結果を変数に代入」の節で紹介したバッククォート`で囲む方法と、計算用コマンドexprを組み合わせることで、整数が代入された変数同士を四則演算し、結果を変数に代入といったことができる。

A=100
B=300
C=`expr $A + $B`
echo "${A}+${B}=${C}"

これを実行すると、以下のようになる。

./test.sh
100+300=400

exprの計算範囲

exprの計算範囲は、signed long long intsigned int64)である。

expr 4294967295 + 4294967295
expr -4294967295 - 4294967295
expr 4611686018427387903 + 4611686018427387903
expr 4294967295 - 4611686018427387905
expr 9223372036854775807 + 9223372036854775807

これを実行すると、以下のようになる。

./test.sh
8589934590
-8589934590
9223372036854775806
-4611686014132420610
expr: +: 計算結果は範囲外の値です

実数の計算

計算用コマンドexprでは、複雑な計算や実数の計算が行えない。そこでプログラム計算用bcを利用すれば、実数計算などが可能である。

使い方はパイプで計算したい文章をbcに渡せばよい。

A=123.45
B=100000
C=`echo "${A} + ${B}" | bc`
echo "${A}+${B}=${C}"

これを実行すると、以下のようになる。

./test.sh
123.45+100000=100123.45

比較

数値の比較

大なり、小なり、等しい、等しくない

シェルスクリプトにおけるif文は、if [ ];thenfiの組み合わせによって定義できる。数値の比較では、[ ]内で数値同士を、-ltなどで比較させる。

# if(1 < 2)
if [ 1 -lt 2 ] ; then
	echo "ok"
fi

# if(1 <= 2)
if [ 1 -le 2 ] ; then
	echo "ok"
fi

# if(1 == 1)
if [ 1 -eq 1 ] ; then
	echo "ok"
fi

# if(1 == 1)
if [ 1 -ne 2 ] ; then
	echo "ok"
fi

# if(1 >= 1)
if [ 1 -ge 1 ] ; then
	echo "ok"
fi

# if(2 >= 1)
if [ 2 -gt 1 ] ; then
	echo "ok"
fi

覚え方

eqequal。nenot equalなので覚えやすい。

ltなのかgtで迷うことがある。less than(より小さい)とgreater than(より大きい)から来ているが、英語が苦手の場合は、左側が小さいのleft、右側が小さいのrightと覚えたりもできる。

ltなのかleで迷うことがある。eequal(=)なので、eがついているle<=と覚えたりできる。

スペースを空けること

たまにやってしまうのだが、スペースを適切に入れる必要がある。

if [ 1 -eq 1 ];then
  ‾ ‾ ‾   ‾ ‾
	echo "ok"
fi

上記のようにオーバーラインがある部分は必ず1スペース以上空ける必要がある。]の右側からはは好きにスペースをいれなくてもよいし、thenは次の行に書いてもよい。

ちなみにスペースを空けないと以下のようなエラーが出る。一応行番号が出るのですぐに分かると思う。

./test.sh: 4: ./test.sh: [1: not found
./test.sh: 4: [: 1-eq: unexpected operator
./test.sh: 4: [: missing ]

等号/不等号の記号を使った書き方

大なりや小なりを使用せずに、単に等しいか等しくないかだけを見るのであれば、後程紹介する文字列用の比較(等号記号、不等号記号)を用いて以下のようにも書ける。記号を使用しているため分かりやすい。

if [ 1 = 1 ];then
	echo "ok"
fi

if [ 1 != 2 ];then
	echo "ok"
fi

ただ、文字列用の比較であるため、0が上部に入ると正しく条件文が通らないので注意が必要である。

# 以下は文字列としては等しくない
if [ 1 = 01 ];then
	echo "ok1"
fi

# 以下は数値的に等しい
if [ 1 -eq 01 ];then
	echo "ok2"
fi

数値の精度について

数値の比較は、signed long long intsigned int64)まで計算可能である。

if [ 2147483647 -lt 4294967295 ] ; then
	echo "unsigned long"
fi

if [ -1 -lt 2147483647 ] ; then
	echo "signed long"
fi

if [ 9223372036854775807 -lt 18446744073709551615 ] ; then
	echo "unsigned int64"
fi

if [ -1 -lt 9223372036854775807 ] ; then
	echo "signed int64"
fi

これを動作させると以下のようになり、unsigned int64での比較に失敗している。

unsigned long
signed long
./test.sh: 11: [: Illegal number: 18446744073709551615
signed int64

小数点の比較について

ltrtは整数の比較ができるのだが、小数点の比較は不可能である。どうしても比較したい場合は、計算処理用のbcを利用するとよい。

具体的には、比較文をbcで計算すると01が返るので、その計算式をバッククォート(`)で囲って利用するとよい。

if [ `echo "1.5 < 2.5" | bc` -eq 1 ] ; then
	echo "ok"
fi

if [ `echo "1.5 > 2.5" | bc` -eq 1 ] ; then
	echo "ng" # 実行されない
fi

文字列の比較

上記で少し紹介したが、文字列は文字列で専用の比較文がある。

等しい、等しくない

文字列の比較は、等号=と不等号!=の記号で記載できる。数値と違って分かりやすいので好き。

# if("a b c" == "a b c")
if [ "a b c" = "a b c" ];then
	echo "ok1"
fi

# if("abc" != "a b c")
if [ "abc" != "a b c" ];then
	echo "ok2"
fi

文字の有無

-z-nを使用すると、文字列の長さが0かそうではないか、つまり文字があるかないかの判定ができる。

# 長さが0なら真
if [ -z "" ];then
	echo "ok"
fi

# 長さが0より大きければ真
if [ -n "   " ];then
	echo "ok"
fi

注意点

当たり前だが、文字列の比較では数値用の比較文が利用できない

# これはできない
if [ "a b c" -eq "a b c" ];then
	echo "ok1"
fi

# これもできない
if [ "abc" -ne "a b c" ];then
	echo "ok2"
fi

これを実行すると、以下のようなエラーが発生する。

./test.sh: 4: [: Illegal number: a b c
./test.sh: 9: [: Illegal number: abc

また、これはやりがちだが、しっかり"でくくらないといけない。括らなくてもエラーはおきないが、半角スペースが混じると問題が発生する。

if [ abc = abc ];then
	echo "ok1"
fi

if [ a b c = a b c ];then
	echo "ok2"
fi

これを実行すると、以下のようなエラーが発生する。

ok1
./test.sh: 7: [: a: unexpected operator

ファイルの比較

ファイルの有無によって条件を変えることがよくあるため、専用の比較式が用意されている。

ファイルの有無の判定

-f-eとファイルパスを組み合わせることで、ファイルの有無の判定が行える。

# ファイルがあるなら真
if [ -f "/etc/crontab" ];then
	echo "ok"
fi

# ファイルがないなら真
if [ -n "/etc/123456" ];then
	echo "ok"
fi

ファイルの種類の判定

私はあまり使わないが種類によっても判定することができる。

# ディレクトリであれば真
if [ -d "/bin/" ];then
	echo "ok1"
fi

# 実行可能であれば真
if [ -x "/bin/echo" ];then
	echo "ok2"
fi

# シンボリックリンクであれば真
if [ -n "/bin/sh" ];then
	echo "ok3"
fi

# 読み取り可能であれば真
if [ -r "/etc/machine-id" ];then
	echo "ok4"
fi

# 書き込み可能であれば真
if [ -w ~/.bashrc ];then
	echo "ok5"
fi

その他の判定

上記では紹介しきれていないが、他にも以下のようなのが存在する。

  • -s ファイルが存在したうえで1バイト以上である。
  • -S ソケット用のファイルである。
  • -nt 左のファイルのほうが新しい(newer than)
  • -ot 右のファイルのほうが新しい(older than)

and と or

条件文の接続

andやorは、意外に簡単に記載できる。

if [ 1 -eq 1 ] && [ "1 2 3" != "123" ] ; then
	echo "ok1"
fi

if [ 1 -eq 1 ] && [ "1 2 3" != "123" ] && [ "123" = "123" ] ; then
	echo "ok2"
fi

if [ 1 -eq 2 ] || [ "1 2 3" != "123" ] ; then
	echo "ok3"
fi

スペースを消しても詰めて書いても問題はない。

if [ 1 -eq 1 ]&&[ "1 2 3" != "123" ] ; then
	echo "ok1"
fi

注意点

C言語と違って左優先である。

if [ 0 = 1 ] && [ 0 = 1 ] || [ 1 = 1 ] ; then
	echo "実行される"
fi

if [ 1 = 1 ] || [ 0 = 1 ] && [ 0 = 1 ] ; then
	echo "実行されない"
fi

優先度の変更

条件文の接続に対して、計算の優先度の変更は使用できない。

bashであれば、括弧()でグループ化することで優先度を変更可能である。

else

elseは普通に利用できる。

if [ 1 -eq 1 ] ; then
	echo "1"
else
	echo "2"
fi

注意点

たまにやってしまうのだが、if/else文内に何も処理を書かないとエラーが発生する。

if [ 1 -eq 1 ] ; then
	echo "1"
else
	# 書かない
fi

if [ 1 -eq 2 ] ; then
	# 書かない
else
	echo "2"
fi

これを実行すると、次のようなエラーが発生する。

./test.sh: 7: ./test.sh: Syntax error: "fi" unexpected
./test.sh: 12: ./test.sh: Syntax error: "else" unexpected

後で何か処理を入れようとして、何も処理を入れないということはやるのだが、エラーが出てしまうので面倒である。

else if

else ifelifで記載ができる。

if [ 1 -eq 2 ] ; then
	echo "1"
elif [ 2 -eq 2 ]; then
	echo "2"
fi

if文内の値による動作の違い

ブラケットの中に何も書かなかったり、長さ0の文字列、0、1を入れるとどうなるかを確認する。

# エラーが発生
if [] ; then
	echo "0"
fi

# 中は実行されない
if [ ] ; then
	echo "1"
fi

# 中は実行されない
if [  ] ; then
	echo "2"
fi

# 中は実行されない
if [ "" ] ; then
	echo "3"
fi

# 中は実行される
if [ 0 ] ; then
	echo "4"
fi

# 中は実行される
if [ 1 ] ; then
	echo "5"
fi

# 中は実行される
if [ "1 1" ] ; then
	echo "6"
fi

# エラーが発生
if [ 1 1 ] ; then
	echo "7"
fi

これらの動作から、以下が分かる。

  • ブラケットの中身が1バイト以上の文字列なら真
  • ブラケットの中身が0バイトの文字列なら偽

switch

シェルスクリプトにおけるswitch文は、case ... inesacとその間に* ) ... ;;を入れることで動作させられる。個人的にダブルセミコロンが見慣れていないので違和感がある。

caseの中には以下のような文字列を使用することが出来る。

  • "で囲った完全一致
  • |を使用してOR条件
  • *を使用してワイルドカード
  • ?を使用して任意の値
  • []で囲ってかっこ内のいずれかの文字
  • [!]で囲ってかっこ内のいずれか以外の文字

例として、以下のように書くことが出来る。(変数を使用しているが、変数の説明は下の章で解説する。)

input="aaa"

case "$input" in
	# 完全一致
	"abc" ) echo "1" ;;
	# OR条件
	"y" | "n") echo "2" ;;
	# ワイルドカード
	aaa* )
		echo "3" ;;
	# 2文字目が任意の1文字
	a?c )
		echo "4"
		;;
	# 1文字目が大文字英数
	[A-Z]* )
		echo "5"
		break
		;;
esac

コード上分かるように、)の後に実行文を書いてもいいし、次の行に書いても良い。同じく、ダブルセミコロン;;も次の行に書いても良い。

指定した文字以外の指定で[!A-Z]と書くのではなく、[^A-Z]のように正規表現風に書くこともできる。ただ、あくまで正規表現風であり、これ以外は利用できないと思われる。

繰り返し文

シェルスクリプトでは、無限ループwhileと、指定したデータを順番に選択するfor文が利用できる。両方ともC言語でいう、breakcontinueが利用できるので、組み合わせることで自由度が上がる。

while文

while;do の間に条件を入れて以下のように記載できる。

i=0
while [ ${i} -lt 5 ]; do
	echo ${i}
	i=`expr ${i} + 1`
	sleep 1
done

これを実行すると、以下のようになる。

./test.sh
0
1
2
3
4

条件文を:にすると無限ループになる。

i=0

while :;do
	if [ ${i} -ge 5 ] ; then break ; fi
	echo ${i}
	i=`expr ${i} + 1`
	sleep 1
done

for文

以下のように、予め区切っておいたデータの回数だけ実行できる。

for i in 0 1 2 3 4 ;do
	echo ${i}
	sleep 1
done

例えば、grepと組み合わせて、繰り返すデータだけ抽出させて実行といったこともできる。

csv="A,B,C"
csv_arr=`echo "${csv}" | grep -oE --color=never "[^,]+"`

for i in ${csv_arr} ;do
	echo ${i}
	sleep 1
done

これを実行すると、ABCと表示される。

関数

使い方

関数名の後に()を書き、その後ブロック{}で囲むことで関数を定義できる。

スクリプトを実行した際の特殊変数$*$#$1$2、……などが利用できる。

MyFunc() {
	echo "引数 \"$*\""
	echo "引数の数 \"$#\""
	echo "1つ目の引数 \"$1\""
	echo "1つ目の引数 \"$2\""
}

echo "test 1"
MyFunc 0

echo "test 2"
MyFunc 1 2

これを実行すると、以下のようになる。

./test.sh
test 1
引数 "0"
引数の数 "1"
1つ目の引数 "0"
1つ目の引数 ""
test 2
引数 "1 2"
引数の数 "2"
1つ目の引数 "1"
1つ目の引数 "2"

ローカル変数

関数内でしか利用しない変数は、修飾子にlocalを付ければよい。localで宣言する前に変数を呼び出すと、スクリプト空間の変数が使用される。

NUM=1

MyFunc1() {
	echo "MyFunc1-1 ${NUM}"
	local NUM=2
	echo "MyFunc1-2 ${NUM}"
}

MyFunc2() {
	NUM=3
}

echo "1 ${NUM}"
MyFunc1
echo "2 ${NUM}"
MyFunc2
echo "3 ${NUM}"

これを実行すると、以下のようになる。

./test.sh
1 1
MyFunc1-1 1
MyFunc1-2 2
2 1
3 3

関数の戻り値で数値を返す

戻り値はreturnで定義ができる。戻り値は数値のみが指定でき、returnを省略した場合の戻り値は0になる。

MyFunc() {
	if [ $1 -ne 0 ] ; then return 1; fi
	return 0
}

MyFunc 0
# 数値の戻り値
echo $?

MyFunc 1
# 数値の戻り値
echo $?

これを実行すると、以下のようになる。

./test.sh
0
1

関数の戻り値で文字列を返す

returnを使用した場合、数値しか戻り値で返すことができない。文字列を返す方法としては、標準出力echoとバッククォート`を組み合わせる方法がある。

MyFunc() {
	echo "あいうえお"
	return 0
}

text=`MyFunc`
# 文字列の戻り値
echo "${text}"

これを実行すると、以下のようになる。

./test.sh
あいうえお

配列

使い方

#!/bin/bashなら利用できるが、今回使用している#!/bin/shには配列の機能が用意されていない。

以下は、配列の長さを調査する関数と、配列を取り出す関数を作ることで、疑似的に配列を利用する例である。

# 配列から行数を取得する関数
GET_ARRAY_LENGTH() {
	local INPUT_TEXT="${1}"
	local LINE_NUM=0
	for LINE_I in ${INPUT_TEXT} ; do
		LINE_NUM=`expr ${LINE_NUM} + 1`
	done
	echo ${LINE_NUM}
}

# 配列から指定した行を取り出す関数
GET_ARRAY_LINE() {
	local INPUT_TEXT="${1}"
	local TARGET_LINE=${2}
	local LINE_NUM=0
	for LINE_I in ${INPUT_TEXT} ; do
		if [ ${TARGET_LINE} -eq ${LINE_NUM} ] ; then
			echo "${LINE_I}"
			return 0
		fi
		LINE_NUM=`expr ${LINE_NUM} + 1`
	done
	echo ""
	return 1
}

DATA="ABC 123 あいうえお"
LEN=`GET_ARRAY_LENGTH "${DATA}"`
echo "配列の長さは ${LEN}"

i=0
while [ ${i} -lt ${LEN} ]; do
	LINE=`GET_ARRAY_LINE "${DATA}" "${i}"`
	i=`expr ${i} + 1`
	echo "${i}行目は、\"${LINE}\""
done

これを実行すると、以下のようになる。

./test.sh
配列の長さは 3
1行目は、"ABC"
2行目は、"123"
3行目は、"あいうえお"

入力のDATA変数の部分は、「for文」の節で紹介したようにgrep -oEの結果などを利用するとよい。

include

使い方

他のファイルで変数の定義や、関数の定義を書いておき、それを環境変数として利用したい場合がある。この場合は、変数が定義されているファイルを.を付けて呼び出すとよい。

  • environment.sh
#!/bin/sh

A=123
  • test.sh
#!/bin/sh

echo "\"${A}\""

. ./environment.sh

echo "\"${A}\""

これを実行すると、以下のようになる。

./test.sh
""
"123"

補足として、.の代わりにsourceを使うスクリプトを見かけることがあると思うが、これは、bashの機能であり、今回のshでは利用できない。

テクニック

スクリプトがある場所をカレントディレクトリにする

ファイル名を消してフォルダ名のみにするdirname、スクリプトのファイルパスを示す$0を組み合わせて、以下のように実行することで、シェルスクリプトがあるディレクトリをカレントディレクトリとします。

# シェルスクリプトがある場所をカレントディレクトリにする
cd `dirname ${0}`

スクリプトが2つ以上起動しないようにする

指定したスクリプト名のプロセスIDを調査するpgrep、これに-cをつけてカウントさせるのを組み合わせることで、多重起動を防止できます。

# 自分を含めて2つ以上起動しているのでやめる
if [ 2 -ge `pgrep -cf "/bin/sh ${0}"` ] ; then
	return 1
fi

shとbashの違い

最後に、今回の記事中で出てきた、shとbashの違いを紹介。bashの方が高機能であり、以下の表以外に様々な機能が利用できる。

shbash
シバン#!/bin/sh#!/bin/bash
文字列切り出し利用不可 cutを使用する${VALUE:0:1} のように切り出す
優先度利用不可利用可能
配列利用不可利用可能
include.を利用sourceを利用

おわりに

おつかれさまです。

少しまとめる予定が結構長くなってしまいました!

タイトルとURLをコピーしました