シェルスクリプトのTIPS
最近はPyrhonやRubyなど他のスクリプト言語も流行っていますが、Unixエンジニアと言えばシェルスクリプト。この記事は基本的なシェルスクリプトがかける人向けのTIPSです。他人が書いたシェルスクリプト読んでいて、「なんだこれ?」と思った時の参考にして下さい。
◆安全なSHEBANGについて(/bin/shの実体)
昨今、多くのUnixOSの/bin/shはBashとなっているが、実はすべてのUnix/Linuxがそうなっているとは限らない。AshだったりZshだったりDashだったりの可能性もある。なので、スクリプトの一行目に書く安全なシェバンは、シェルを明示しておく。
#!/bin/bash
もしくはすべてのUnixシェルで実行できるよう古典的で安全な(PORTABLEな)シェルスクリプトを記述しておくと良い。ただし、実行速度が低下したり、配列が使えない、EXPRコマンドを用いたインクリメントがトリッキーなどの制限がある。
◆DEBUGモード
どこでエラーが起きているのかわからないが正常終了しない場合、Xオプションを付けてDEBUGモードで実行する。シェルプログラミングの場合、これとPRINTFデバッグで大方は解決する。
# /bin/bash -x myscript.sh
◆インクルード
ドットの後にパラメータや自作の関数などを定義したファイルを指定すれば、実行時に読み込まれる。パラメータ専用ファイルなどを作成し、プロジェクトごとに編集すれば直接スクリプトを編集するより安全にカスタマイズ・デプロイが可能となる。
#!/bin/bash
. /usr/dev/param
. /user/dev/functions
#(以下省略)
◆ワンライナー1 セミコロンで複数行を1行に収める
for name in foo bar foo2
do
echo ${name}
done
セミコロンを用いて一行で書くと次のように表せる
for name in foo bar foo2; do echo ${name}; done
ワンライナーの良い点として可読性は低下するがコマンドラインで気軽に投入が可能で、高度なコマンドを運用マニュアルに組み込むことができる。
◆ワンライナー2 異なる記述で同じ処理をさせる
if [ -e /var/tmp/test ]
then
rm /var/tmp/test
fi
前のコマンドが正常終了(終了コードはTrue)した場合、&& 以降の処理を実行する。
頻出の記述法なので知っておくと〇
[ -e /var/tmp/test ] && rm /var/tmp/file
よく知られた話だが if [ ] then ~ の [ は、実体は /bin/test のエイリアス(別名)である /bin/[ であり、次のように記述することができる
/bin/test -e /var/tmp/test && rm /var/tmp/file
&&と逆で、直前のコマンドが異常終了(終了コードがTrueでない)した場合、|| 以降の処理を実行する。
[ の後の ! はNOTを意味し、ファイルが存在しない場合を表す。
# testファイルが存在しない場合、作成しなさい
if [ ! -e /var/tmp/test ]
then
touch /var/tmp/test
fi
下のように書くことが可能。
[ -e /var/tmp/test ] || touch /var/tmp/test
◆&&と||を利用したワンライナーのハマりポイント
よくやる間違いで下記のコードは意味が異なるので注意する。
test_Aが成立している場合、コマンドBを実行し、そうでない場合はコマンドCを実行する
If [ test_A ]
then
command_B
else
command_C
fi
[ test_A ] && command_B || command_C
上記ではtest_Aが成立している場合、コマンドBを実行する、そうでない場合は、コマンドCを実行する。ところが、test_Aが成立しておりコマンドBの実行した後、コマンドBの実行結果が失敗だった場合はコマンドCが実行されてしまう (ココがハマリポイント)
◆逆ワンライナー 1行で書ける処理を複数行に分割する
スクリプトの可読性を高める場合に利用される。
cat /var/tmp/test.log | grep -v ^# | sed -^#/d | egrep 'foo1|foo4|foo5' | awk '{print $1,$3,$7,$11}' | sort -e | sed -e 's/foo/bar/g' > /var/tmp/tmp.log.txt
cat /var/tmp/test.log | \
grep -v ^# | \
sed -e '^$/d' | \
egrep 'foo1|foo4|foo5' | \
awk '{print $1,$3,$7,$11}' | \
sort -u | \
sed -e 's/foo/bar/g' \
> /var/tmp/tmp.log.txt
◆[ と [[ の違い
シェルスクリプトでは正規表現と組み合わせて[[がパターンマッチングで利用されます。
クォートしていない場合、パターンマッチングの結果、fooはfo[ou](fooもしくはfouのどちらか)に該当するためTrueが返されます。
ただし、[[でもクォートしている場合、文字列解釈の結果は変わらないためFalseが返されます。
# 文字列はアンマッチ
(bash)# [ qiita == 'qiit[aiueo]' ]; echo $?
1
# 文字列はマッチ
(bash)# [[ qiita == qiit[aiueo] ]]; echo $?
0
# 文字列はアンマッチ
(bash)# [[ qiita == 'qiit[aiueo]' ]]; echo $?
1
その他に大きな違いとしては、文字列のパターンマッチングに利用される他、[[の場合、文字列に数式が書かれている場合は文字列と解釈せず、計算を行ってからテストが実行される。こちらも覚えておくと良い。
(bash)# char='10 + 10'
(bash)# [ ${char} -eq 20 ]; echo $?
-bash: [: 10+10 integer expression expected
(bash)# [[ ${char} -eq 20 ]]; echo $?
0
◆インクリメント・デクリメント
外部コマンド expr を用いて計算。exprのプロセスをforkするコストが高いためスクリプトの実行速度はやや遅くなるが、どのUnixシェルでも動作するため安全でポータブル性が高い書き方となる。
cnt=`expr ${cnt} + 1`
cnt=`expr ${cnt} - 1`
BASHのビルドイン機能。以下の3つの書き方だと実行結果は高速だが、他のシェルで動かない場合もあるため、BASHの脆弱性が見つかった場合などに他のシェルに切り替えて実行するなどの対策は打てなくなるというデメリットがある。
((cnt++))
let cnt++
let ++cnt
※デクリメントは--にしてください
◆ユーザによる入力を受け付ける
ITやCASEを利用してユーザによる値の入力や選択を記述できる。入力文字列を表示させたくない場合はSオプションを利用する。
(bash)# read str
aiueo <ENTER>
(bash)# echo ${str}
aiueo
◆分岐先で何もしたくない時はコロン
if elseやcaseにいろいろ分岐を書いていたけれど、いくつかの処理をとりやめたい場合でも、後に再度利用しそうだったり、条件式やパラメータなどが非常に重要で消したくなかったりする場合、その分岐構造そのものは残しておきたかったりする場合があります。そういう場合は処理部をコメントアウトし替わりに : を入れておきます。
if [ -e /usr/dev/functions ]
then
. /usr/dev/functions
else
# (停止したい処理はコメントアウト)
:
fi
◆yesコマンド
文字列yと終了コード0を超高速で延々と返す謎のコマンドyes。
(bash)# yes
y
y
y
y
使う場面が思い浮かばないと思いきや、無限ループさせたい場合に使ったりします。
#!/bin/bash
. /usr/dev/functions
. /usr/dev/param
while [ yes ]
do
sleep ${interval}
chk=$(ps -ef | grep -c ${srvs})
if [ ${chk} -lt 2 ]
then
# function alert_to
aleat_to all_engineer@foobar.co.jp
# funtion reboot_services
reboot_services ${srvs}
fi
done
◆スクリプトの簡単なDaemon化
常駐化させてデーモン化(サービス化)させたい場合なんかにはnohupを使います。以下のスクリプトですと、process_chk.shはログアウト後も常駐プロセスとなり働き続けてくれます。
(bash)# nohup /bin/bash process_chk.sh &
◆スクリプトの実行結果を画面でも見たいし、同時にファイルにも保存したい
(bash)# ./chk.sh | tee -a /var/tmp/result.log
OK
OK
NG
◆精密な計算を行いたい
exprコマンドは整数しか扱えないため、小数点何桁といった計算を行うにはbcコマンドを利用する。デフォルトでは小数点以下は表示しないので、scaleで桁数を指定する。
#!/bin/bash
result=$(echo "scale=1; 101354 * 1.08" | bc)
echo ${result}
◆ランダムな数値を得る
BASHのシェル変数を利用するパターン
(bash)# echo ${RANDOM}
8732
BASHの機能を利用せず安全でポータブルな方法を検討した結果、時間の文字列でハッシュをとれば良いんじゃないかというアイデアが上手くいった
(bash)# date | md5sum | tr -dc "0123456789"
87329348
◆ランダムな文字列を得る
先ほどの手法を再利用して、ランダムな文字列を得る。これらを組み合わせれば任意のパスワードなどを生成することができる。
(bash)# date | md5sum | tr -dc "abcdefghijklmnopqrstuvwxyz"
ndrtnrvlkjr
◆typesetとdeclare
どちらも同じ変数を宣言する命令。typedefはKSHなど他のシェルでも利用が可能で古典的かつポータブル。declaireの方が新しい。
オプション | 内容 |
---|---|
a | 配列を宣言 |
i | 数値を宣言 |
r | 読み出し専用を宣言 |
p | 宣言した変数の値を表示 |
他にもあれば適当に更新の予定。