初級者向けに『自分がシェルスクリプト書くときに気をつけていること』をまとめてみました。
@masudaKの『シェルスクリプトを書く際に気を付けていること8箇条』の乗っかりエントリです。
内容は重複しないように書いてますので合わせて読んでください!
基本的にLinuxの/bin/sh、/bin/bashを想定しています。
テキスト処理は標準入力から受け取って標準出力に出す
テキストを扱う小さなツールを作りましょう。
引数はオプション情報を渡すのに使います。
そうすればgrep、sort、uniqなどの便利なコマンドとパイプで連携できます。
grep ERROR /tmp/test.log | my_cmd1.sh 192.168.1.1 | sort
全てを実行する1つのスクリプトを作るのはたいへんだし、応用が効かないです。
文字列中の変数は{}でかこむ
文字列中に変数を埋め込む場合は変数名を{}で囲むようにしています。
var=abc ls $var # OK ls /tmp/$var1.xml # ダメ ls /tmp/${var}1.xml # OK ls /tmp/$var.xml # OKなんですが ls /tmp/${var}.xml # 統一したほうが見やすいと思います
変数を""で囲む
外部入力から変数をセットする場合は特に要注意です。
シェルスクリプトを書き慣れてないプログラマがif文書くとよくこうなってます。
if [ $var = "abc" ];then echo true fi
これは変数が空だった場合はこう解釈されて
if [ = abc ];then echo true fi
エラーになっちゃいます。(まぁそれでもたいてい動きますが・・・)
test.sh: line 4: [: =: unary operator expected
ちゃんとクオートしましょう。
if [ "$var" = abc ];then # 逆にabcはクオートしなくてもいいです echo true fi
他にも変数に「*」が入っててファイル名展開されちゃうなど、制御文字系の予期せぬ動作を防ぐことに役立ちます。
ヒアドキュメントを使う
ミドルウェアの設定ファイルをテンプレート化したい場合など、sed等でやろうとするとけっこうたいへんです。
echoの引数に埋め込んでもいいのですが、ヒアドキュメントが見やすいと思います。
DOCUMENT_ROOT=$1;shift DOMAIN=$1;shift cat <<END > /etc/httpd/conf.d/vhost_${DOMAIN}.conf <VirtualHost *:80> ServerName ${DOMAIN} DocumentRoot ${DOCUMENT_ROOT} <Directory ${DOCUMENT_ROOT}> Order deny,allow Allow From all </Directory> </VirtualHost> END
テンプレートファイルが複雑で外出ししたい時はphpやeRubyなどを使うことが多いです。
ftpなどの対話コマンドへの流し込みなどにも便利ですし
#!/bin/sh SERVER=$1 USER=$2 PASS=$3 FILE=$4 ftp -n <<END open $SERVER user $USER $PASS cd /tmp binary prompt put $FILE END
コマンドラインからもよく使います
$ cat <<END | grep -i error <メモ帳などから貼付け> END
-xを使って処理を追う
デバッグ時や処理の流れを目視確認したい時はxオプションを使いましょう。
sh、bashにつけてスクリプトを実行すると処理の流れが追えます。
$ sh -x ./test.sh + var=abc + '[' abc = abc ']' + echo true true
もちろんスクリプトのアタマで指定してもOKです。
#!/bin/sh -x var="abc" if [ $var = "abc" ];then echo true else echo false fi
設定ファイルを外に出す
設定切り替えなど必要そうなら変数定義を外出ししましょう。
別ファイルに変数を定義して
VAR1=aaa VAR2=bbb
「.」かsourceで読みこみます
#!/bin/sh . /tmp/test.conf echo $VAR1 echo $VAR2
ファイル指定を相対パスにする
必要なファイルをディレクトリにまとめたとき、設定ファイルなどを単純に相対パスで指定すると
#!/bin/sh . conf/test.conf echo $VAR1 echo $VAR2
『特定のディレクトリじゃないと実行できないスクリプト』ができちゃいます。
$ ./test.sh aaa bbb $ cd .. $ test/test.sh test.sh: line 4: conf/test.conf: そのようなファイルやディレクトリはありません
どこに置いても動くようにしたい場合は実行スクリプトを基点にしてcdしてやります。
dirnameと$0(実行スクリプトのパス)を使います。
#!/bin/sh cd `dirname $0` || exit 1 . conf/test.conf echo $VAR1 echo $VAR2
PATHを明示する
cronなど対話シェル以外で使う場合、~/.bash_profile等が読み込まれずにPATHが意図しないものになっている可能性があります。
あと/usr/local/binや~/binなどをコマンド検索に入れたくない場合もあるでしょう。
そういう時は明示的に指定してやります。
PATH=/bin:/usr/bin export PATH
互換性維持のために使うコマンドを全部定義する人もいますが、
LS=/bin/ls $LS /tmp
面倒だし見づらいので好きじゃないです・・・
``ではなく$()を使う
ネスト時するときに便利です。
# こういう場合はいいですが count=`wc -l /tmp/test.log.20120226 | cut -d" " -f1` # ネストすると見づらいし、挙動がわかりづらいです! count=`wc -l /tmp/test.log.\`date +%Y%m%d\` | cut -d" " -f1` # こういう場合は$()を使いましょう count=$(wc -l /tmp/test.log.$(date +%Y%m%d) |cut -d" " -f1)
同じようなのに『算術演算にexprじゃなく$(())を使う』というのもあります
i=3 echo `expr $i \* 3` echo $((i * 3))
終わりに、と最後のポイント
職業柄、シェルスクリプトはよく書いていますが、書く前に意識するのが『シェルスクリプトでがんばりすぎないこと』です。
自分がシェルスクリプトを使うのはだいたいこういう目的です。
- 普段、手でやってる作業を自動化する
- 行単位の簡単なテキスト処理
以下のような場合にはたいてい別の言語を使うことを検討します。
- 配列やハッシュマップなどのデータ構造を使いたい
- 複雑なオプションを使って高度に処理を制御したい
自分の場合、Perlが多いですが、PythonもOS制御、ネットワーク系のライブラリが揃っていて良さそうだなと最近は思ってます。
あとは、perlやawkのワンライナーをぜひ覚えましょう。
できることがガッと広がります。
書ききれなかったTIPS系のネタがいろいろあるのでそのうち『その2』を書きます!!!