はじめに
GoogleさんがShellスタイルガイドを共有していたので、いくつか気になった点をピックアップしました。
自分のShellスタイルはかなり我流なので、自省の意味も込めてコメントも併記します。
Googleスタイルガイドの元ネタ (Python/C++/Java/Rとかだけでなくdocumentガイドなど色々あります)
https://github.com/google/styleguide
Shellスタイルガイド (今回はこちら)
http://google.github.io/styleguide/shell.xml
本当は人間がチェックするのではなくcpplintのためXML定義なのかもですが、気にしない気にしない。
(見たところcpplintはc++だけだと思ってます)
commitフックでshell系のlint走らせろっていうのが今風なのかもしれませんが、キニシナイキニシナイ。
拾い読み
1. 利用可能なShellについて
Bash
のみ利用可能。従って#!/bin/bash
で始めましょう。
本来の機能を壊さないようShellのオプションを使うためのフラグを多用しないこと。
set
を使いましょう。
2. 利用シーン
小さいツールやスクリプト実行時のラップ程度にShellの利用を抑えること。
100行超えるとPythonとか他の言語を使おうこと。
雑記
(ちょっとしたツール目的でも100行すぐ超えちゃいますね。)
3. SUID/SGIDについて
セキュリティ的な課題や他の実機への横展開が難しくなるのでSUID/SGID
の利用は禁止。
どうしても必要ならsudo
使おう!
雑記
(そもそもSUID使ったことがなかったので問題なし!)
4. 標準出力・標準エラー出力
全てのエラーメッセージはSTDOUT
ではなくSTDERR
に出力すること。
正常な状態と異常な状態を簡単に区別できるようにするためです。
下記のような関数を作っておくのがおススメ。
err() {
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $@" >&2
}
if ! do_something; then
err "Unable to do_something"
exit "${E_DID_NOTHING}"
fi
雑記
(デバッグ用に出力するときは何も考えずにecho "hogehoge"
と標準出力してました。反省。)
5. コメント
- ファイル
- Shellの機能を示す簡潔なトップレベルコメントを必ず書くこと。
- 関数
- ステップ数・複雑度に関係なく全ての関数にコメントを付けること。
- グローバル変数、引数、戻り値が何か記載すること
#!/bin/bash
#
# データベースのバックアップ用途
export PATH='/usr/xpg4/bin:/usr/bin:/opt/csw/bin:/opt/goog/bin'
#######################################
# Cleanup files from the backup dir
# Globals:
# BACKUP_DIR
# ORACLE_SID
# Arguments:
# None
# Returns:
# None
#######################################
cleanup() {
...
}
雑記
(Utilityぽい関数ではノーコメント派でした。心を入れ替えます。)
6. TODO
一時的にTODOを使う場合は、ユーザ名と補足内容を書くこと。
Issueの番号も書ければ望ましい。
# TODO(user_name): Handle the unlikely edge cases (bug ####)
7. インデント
タブではなく、2スペース。
タブダメ絶対。
雑記
(タブ派なのでそのまま受け取ると消耗しますね)
8. パイプ
1パイプなら1行。
複数行なら改行して、2スペースを開けてパイプで繋げましょう。
# All fits on one line
command1 | command2
# Long commands
command1 \
| command2 \
| command3 \
| command4
雑記
(あんまり行数増やしたくないんで、今までは長くなったら適当に改行してました。
ls -l | grep ^d | awk '{print $NF}' | sort | uniq -c
とか1行で書きたいと思うんですけど、
規約を厳格に適用すると4行になっちゃいますね)
9. ループ
;
とdo
とthen
は、for
とwhile
とif
と同じ行に書くこと。
for dir in ${dirs_to_cleanup}; do # ★セミコロンやdoはforと同じ行に書く
if [[ -d "${dir}/${ORACLE_SID}" ]]; then
log_date "Cleaning up old files in ${dir}/${ORACLE_SID}"
rm "${dir}/${ORACLE_SID}/"*
if [[ "$?" -ne 0 ]]; then
error_message
fi
else
mkdir -p "${dir}/${ORACLE_SID}"
if [[ "$?" -ne 0 ]]; then
error_message
fi
fi
done
雑記
(主観ですがネット上のサンプルでは、do
やthen
の改行派が多そうなイメージです。
でもこっちの方がすっきりして良いですね!)
10.変数
一般的に"$var"
より"${var}"
が推奨です。
ただ、詳細なルールは以下を確認してください。
# 特殊な書き方はそちらを優先
echo "引数インデックス: $1" "$5" "$3"
echo "特殊系: !=$!, -=$-, _=$_. ?=$?, #=$# *=$* @=$@ \$=$$ ..."
# 中括弧必須:
echo "many parameters: ${10}"
# 混乱を避けるために中括弧を付けること
# [出力例]a0b0c0
set -- a b c
echo "${1}0${2}0${3}0"
# 他の変数のため中括弧があると望ましい:
echo "PATH=${PATH}, PWD=${PWD}, mine=${some_var}"
while read f; do
echo "file=${f}"
done < <(ls -l /tmp)
# ダブルクォートや中括弧が無い変数 特殊変数なのに中括弧あり
echo a=$avar "b=$bvar" "PID=${$}" "${1}"
# "${1}0${2}0${3}0"と同じパターン
# ${10}${20}${30}という意味には当然ならないので注意
set -- a b c
echo "$10$20$30"
11. クオート
シングルクォートは置換が必要ないとき。
ダブルクォートは置換が必要なときに利用する。
12. $(command)
バッククォートではなく$(command)が推奨。
var="$(command "$(command1)")"
var="`command \`command1\``"
雑記
(意識せずバッククォート使っている時も多かったので、反省)
13. Test, [ and [[
[[ ... ]]
は[
やtest
より推奨。
[[ ... ]]
が引数なしや複数引数の場合でもエラーにならず、正規表現が使えるため。
# 正規表現使える
if [[ "filename" =~ ^[[:alnum:]]+]]; then
echo "Match"
fi
# f*がカレントディレクトリにの内容に展開され"too many arguments"が出るかも
if [ "filename" == f* ]; then
echo "Match"
fi
14. ワイルドカード
*
の代わりに./*
を使おう。
命名:関数
関数名はlower_caseで命名すること。
# 関数名はlower_case
my_func() {
...
}
# 関数名をcamelCaseにはしない
myFunc() {
...
}
15. 命名:変数
ループ変数は似たような名前にすること。
for zone in ${zones}; do
something_with "${zone}"
done
for z in ${zones}; do # ★変数名をzに。トレードオフだが非推奨
something_with "${z}"
done
雑記
(他の言語でも同じですね)
16. 命名:定数、環境変数
定数や環境変数はSNAKE_CASEにする。
# 定数
readonly PATH_TO_FILES='/some/path'
# 環境変数
declare -xr ORACLE_SID='PROD'
雑記
(よく見かけますね。他の言語でもやりますね)
17. 命名:ファイル名
lower_case.sh
の形式でファイルの命名を行うこと
18. ローカル変数
関数内でしか用いない変数にはlocal
で宣言すること。
グローバルネーム空間の汚染を防ぐことが出来ます。
代入する値がコマンド置換の場合は、localの宣言と分けること。
my_func2() {
local name="$1"
# local変数の宣言と代入はわけること!
local my_var
my_var="$(my_func)" || return
...
}
# コマンド結果を代入する場合は、ワンセンテンスで書かないこと。
# $? の結果はmy_funcの結果ではなく、'local'の結果が格納されるため(!)
local my_var="$(my_func)"
[[ $? -eq 0 ]] || return
雑記
(これ、知らなかったです・・)
19. Conclusion
「常識的な、そして一貫性のあるコーディングをしましょう」
Use common sense and BE CONSISTENT.
まとめ
- コーディング規約を読むと、ハマりどころな観点もまとまっているので勉強になりおすすめです
- Javaとかもそうですが糖衣構文などで複数の書き方があると、規約が必要になってくるので大変ですね。
- (Golangはそういう点がまだ少ない気するのでやはり後発有利)
- みなさまも良いShellライフを!