以下は、2020年11月時点の、Google Shell Style Guide の全文を、kanda.motohiro@gmail.com が、 Google 翻訳にかけ、その結果を修正したものです。原文のライセンスは、CC-By 3.0 なのですが、機械翻訳結果の著作権と使用条件が不明なので、この文書全体の配布ライセンスも不明です。
その他の言語のスタイルガイドの訳:
リビジョン2.02
多くのGoogle社員によって作成、改訂、保守されています。
Bashは、実行可能ファイルに許可されている唯一のシェルスクリプト言語です。
実行可能ファイルは#!/bin/bash と、最小数のフラグで始まる必要があります。スクリプトをbash script_name と呼び出しても機能が損なわれないように、set を使ってシェルオプションを設定します。
すべての実行可能シェルスクリプトをbashに制限すると、すべてのマシンにインストールされる一貫したシェル言語が得られます。
これに対する唯一の例外は、あなたがコーディングしているものによって強制される場合です。この一例は、全てのスクリプトにプレーンなBourneシェルを必要とするSolarisSVR4パッケージです。
シェルは、小さなユーティリティまたは単純なラッパースクリプトにのみ使用する必要があります。
シェルスクリプトは開発言語ではありませんが、Google全体でさまざまなユーティリティスクリプトを作成するために使用されます。このスタイルガイドは、広範な展開に使用することを提案するというよりも、その使用法を認識したものです。
いくつかのガイドライン:
主に他のユーティリティを呼び出していて、データ操作が比較的少ない場合は、シェルがその仕事に適した選択肢です。
パフォーマンスが重要な場合は、シェル以外のものを使用してください。
100行を超える長さのスクリプト、または単純ではない制御フローロジックを使用するスクリプトを作成している場合は、今すぐより構造化された言語でスクリプトを書き直す必要があります。スクリプトが大きくなることを覚えておいてください。後日、より時間のかかる書き直しを避けるために、スクリプトを早めに書き直してください。
コードの複雑さを評価するとき(たとえば、言語を切り替えるかどうかを決定するとき)、コードが作成者以外の人によって簡単に保守できるかどうかを検討してください。
実行可能ファイルには、.sh拡張子または拡張子を付けない(強く推奨)でください 。ライブラリには.sh 拡張子が必要であり、実行可能であってはなりません。
プログラムを実行するときにプログラムがどの言語で書かれているかを知る必要はなく、シェルは拡張子を必要としないため、実行可能ファイルには拡張子を使用しないことをお勧めします。
ただし、ライブラリの場合、それがどの言語であるかを知ることが重要であり、異なる言語で同様のライブラリを用意する必要がある場合もあります。これにより、目的は同じですが、異なる言語のライブラリファイルに言語固有のサフィックスを除いて同じ名前を付けることができます。
SUIDとSGIDはシェルスクリプトでは禁止されています。
シェルにはセキュリティの問題が多すぎるため、SUID / SGIDを許可するのに十分なセキュリティを確保することはほぼ不可能です。bashはSUIDの実行を困難にしますが、それでも一部のプラットフォームでは可能であるため、SUIDの禁止について明示的にしています。
必要に応じて、昇格されたアクセスを提供するためにsudoを使用します。
すべてのエラーメッセージはSTDERRに出る必要があります。
これにより、通常のステータスと実際の問題を簡単に区別できます。
エラーメッセージを他のステータス情報と一緒に出力する関数をお勧めします。
err() {
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2
}
if ! do_something; then
err "Unable to do_something"
exit 1
fi
各ファイルをその内容の説明で開始します。
すべてのファイルには、その内容の簡単な概要を含むトップレベルのコメントが必要です。著作権表示と著者情報はオプションです。
例:
#!/bin/bash
#
# Perform hot backups of Oracle databases.
明白でも短くもない関数はコメントする必要があります。ライブラリ内の関数は、長さや複雑さに関係なくコメントする必要があります。
コードを読まずにコメント(および提供されている場合はセルフヘルプ)を読むことで、他の誰かがプログラムの使用方法やライブラリ内の関数の使用方法を学ぶことができるべきです。
すべての関数コメントは、以下を使用して意図されたAPIの動作を説明する必要があります。
関数の説明。
グローバル:使用および変更されたグローバル変数のリスト。
引数:取られた引数。
出力:STDOUTまたはSTDERRへの出力。
戻り値:最後のコマンド実行のデフォルトの終了ステータス以外の戻り値。
例:
#######################################
# Cleanup files from the backup directory.
# Globals:
# BACKUP_DIR
# ORACLE_SID
# Arguments:
# None
#######################################
function cleanup() {
…
}
#######################################
# Get configuration directory.
# Globals:
# SOMEDIR
# Arguments:
# None
# Outputs:
# Writes location to stdout
#######################################
function get_dir() {
echo "${SOMEDIR}"
}
#######################################
# Delete a file in a sophisticated manner.
# Arguments:
# File to delete, a path.
# Returns:
# 0 if thing was deleted, non-zero on error.
#######################################
function del_thing() {
rm "$1"
}
コードのトリッキー、非自明、興味深い、または重要な部分にコメントします。
これは、一般的なGoogleコーディングコメントの慣例に従います。すべてにコメントしないでください。複雑なアルゴリズムがある場合、または通常とは異なることをしている場合は、短いコメントを入力してください。
一時的、短期的な解決策、または十分であるが完全ではないコードには、TODOコメントを使用します。
これは、C ++ガイドの規則と一致します。
TODOは、すべて大文字のTODO文字列で始まり、問題についての最もよい文脈を持つ人の名前、電子メールアドレスや他の識別子を続けます。目的は、詳細を取得する方法を見つけるために検索できる一貫したTODO形式を用意することです。TODOは、参照された人が問題を修正するという約束ではありません。したがって、 TODOを作成するときに与えられるのは、ほとんどの場合、あなたの名前です。
例:
# TODO(mrmonkey): Handle the unlikely edge cases (bug ####)
変更するファイルにすでに存在するスタイルに従う必要がありますが、新しいコードには次のものが必要です。
2つのスペースをインデントします。タブはありません。
読みやすさを向上させるために、ブロック間に空白行を使用してください。インデントは2つのスペースです。何をするにしても、タブは使用しないでください。既存のファイルについては、既存のインデントを忠実に守ってください。
最大行長は80文字です。
80文字を超える文字列を記述する必要がある場合は、可能であれば、ヒアドキュメントまたは埋め込み改行を使用して行う必要があります。80文字より長くする必要があり、適切に分割できないリテラル文字列は問題ありませんが、短くする方法を見つけることを強くお勧めします。
# DO use 'here document's
cat <<END
I am an exceptionally long
string.
END
# Embedded newlines are ok too
long_string="I am an exceptionally
long string."
パイプラインは、すべてが1つのラインに収まらない場合は、ラインごとに1つに分割する必要があります。
パイプラインがすべて1行に収まる場合は、1行にする必要があります。
そうでない場合は、パイプごとに改行し、パイプの次のセクションに2スペースインデントを使用して、ラインごとに1つのパイプセグメントで分割する必要があります。これは、| を使用して結合された一連のコマンドと、||および&&を使用した論理複合に適用されます。
# All fits on one line
command1 | command2
# Long commands
command1 \
| command2 \
| command3 \
| command4
while、forまたはifと同様に、; doと; thenを同じ行に置きます 。
シェルのループは少し異なりますが、関数を宣言するときの中括弧の場合と同じ原則に従います。それは次のようになります。if/for/while と; then そして; doは同じ行にする必要があります。 elseは独自の行にある必要があり、終了ステートメントは、冒頭ステートメントと垂直に整列した独自の行にある必要があります。
例:
# If inside a function, consider declaring the loop variable as
# a local to avoid it leaking into the global environment:
# local dir
for dir in "${dirs_to_cleanup[@]}"; do
if [[ -d "${dir}/${ORACLE_SID}" ]]; then
log_date "Cleaning up old files in ${dir}/${ORACLE_SID}"
rm "${dir}/${ORACLE_SID}/"*
if (( $? != 0 )); then
error_message
fi
else
mkdir -p "${dir}/${ORACLE_SID}"
if (( $? != 0 )); then
error_message
fi
fi
done
選択肢を2スペースインデントします。
1行の選択肢は、パターンの閉じ括弧の後、;;の前にスペースが必要です。
長いコマンドまたはマルチコマンドの選択肢は、パターン、アクション、および;; を別々の行で複数の行に分割する必要があります。
マッチ式は、caseおよびesacから1レベルインデントされます。複数行のアクションは別のレベルでインデントされます。通常、マッチ式を引用する必要はありません。パターン式の前に開き括弧を付けないでください。;&および;;&表記は避けてください。
case "${expression}" in
a)
variable="…"
some_command "${variable}" "${other_expr}" …
;;
absolute)
actions="relative"
another_command "${actions}" "${other_expr}" …
;;
*)
error "Unexpected expression '${expression}'"
;;
esac
単純なコマンドは、式が読みやすい限り、パターンと ;; と同じ行に配置できます。これは、多くの場合、1文字のオプション処理に適しています。アクションが1行に収まらない場合は、パターンを1行に配置し、次にアクションを配置してから、;; を1行に配置します。アクションと同じ行にある場合は、パターンの閉じ括弧の後と;; の前にスペースを使用します。
verbose='false'
aflag=''
bflag=''
files=''
while getopts 'abf:v' flag; do
case "${flag}" in
a) aflag='true' ;;
b) bflag='true' ;;
f) files="${OPTARG}" ;;
v) verbose='true' ;;
*) error "Unexpected option ${flag}" ;;
esac
done
優先順位:見つけたものと一貫性を保ちます。変数を引用します。"$var" より "${var}" を優先します。
これらは強く推奨されるガイドラインですが、必須の規制ではありません。それにもかかわらず、それが推奨であり、必須ではないという事実は、それが軽視されるべきであることを意味するものではありません。
それらは優先順にリストされています。
既存のコードで見つけたものと一貫性を保ちます。
変数を引用します。以下の引用セクションを参照してください。
厳密に必要な場合や深い混乱を避ける場合を除いて、単一文字のシェルスペシャル/位置パラメータを中括弧で区切ってはいけません。
他のすべての変数を中括弧で区切ることをお勧めします。
# Section of *recommended* cases.
# Preferred style for 'special' variables:
echo "Positional: $1" "$5" "$3"
echo "Specials: !=$!, -=$-, _=$_. ?=$?, #=$# *=$* @=$@ \$=$$ …"
# Braces necessary:
echo "many parameters: ${10}"
# Braces avoiding confusion:
# Output is "a0b0c0"
set -- a b c
echo "${1}0${2}0${3}0"
# Preferred style for other variables:
echo "PATH=${PATH}, PWD=${PWD}, mine=${some_var}"
while read -r f; do
echo "file=${f}"
done < <(find /tmp)
# Section of *discouraged* cases
# Unquoted vars, unbraced vars, brace-delimited single letter
# shell specials.
echo a=$avar "b=$bvar" "PID=${$}" "${1}"
# Confusing use: this is expanded as "${1}0${2}0${3}0",
# not "${10}${20}${30}
set -- a b c
echo "$10$20$30"
注:${var} のように中括弧を使用することは、引用の形式ではありません。「二重引用符」も使用しなければいけません。
注意深く引用符で囲まれていない展開が必要な場合、またはシェル内部の整数(次のポイントを参照)である場合を除き、変数、コマンド置換、スペース、またはシェルメタ文字を含む文字列は常に引用符で囲んでください。
要素のリスト、特にコマンドラインフラグを安全に引用するには、配列を使用します。以下の配列を参照してください。
整数になるように定義されているシェルの内部の読み取り専用の特殊変数: $?、$#、$$、$!(man bash)は、必要に応じて、引用してください。一貫性を保つために、PPIDなどの「名前付き」内部整数変数を引用することをお勧めします。
(コマンドオプションやパス名ではなく)「単語」である文字列を引用することをお勧めします。
リテラル整数は絶対に引用しないでください。
[[ … ]]のパターン一致の引用規則に注意してください。以下の、Test, [ … ]、および[[ … ]]セクションを参照してください 。
メッセージまたはログの文字列に引数を追加するなど、$* を使用する特別な理由がない限り、"$@"を使用してください。
# 'Single' quotes indicate that no substitution is desired.
# "Double" quotes indicate that substitution is required/tolerated.
# Simple examples
# "quote command substitutions"
# Note that quotes nested inside "$()" don't need escaping.
flag="$(some_command and its args "$@" 'quoted separately')"
# "quote variables"
echo "${flag}"
# Use arrays with quoted expansion for lists.
declare -a FLAGS
FLAGS=( --foo --bar='baz' )
readonly FLAGS
mybinary "${FLAGS[@]}"
# It's ok to not quote internal integer variables.
if (( $# > 3 )); then
echo "ppid=${PPID}"
fi
# "never quote literal integers"
value=32
# "quote command substitutions", even when you expect integers
number="$(generate_number)"
# "prefer quoting words", not compulsory
readonly USE_INTEGER='true'
# "quote shell meta characters"
echo 'Hello stranger, and well met. Earn lots of $$$'
echo "Process $$: Done making \$\$\$."
# "command options or path names"
# ($1 is assumed to contain a value here)
grep -li Hugo /dev/null "$1"
# Less simple examples
# "quote variables, unless proven false": ccs might be empty
git send-email --to "${reviewers}" ${ccs:+"--cc" "${ccs}"}
# Positional parameter precautions: $1 might be unset
# Single quotes leave regex as-is.
grep -cP '([Ss]pecial|\|?characters*)$' ${1:+"$1"}
# For passing on arguments,
# "$@" is right almost every time, and
# $* is wrong almost every time:
#
# * $* and $@ will split on spaces, clobbering up arguments
# that contain spaces and dropping empty strings;
# * "$@" will retain arguments as-is, so no args
# provided will result in no args being passed on;
# This is in most cases what you want to use for passing
# on arguments.
# * "$*" expands to one argument, with all args joined
# by (usually) spaces,
# so no args provided will result in one empty string
# being passed on.
# (Consult `man bash` for the nit-grits ;-)
(set -- 1 "2 two" "3 three tres"; echo $#; set -- "$*"; echo "$#, $@")
(set -- 1 "2 two" "3 three tres"; echo $#; set -- "$@"; echo "$#, $@")
ShellCheckプロジェクトは、シェルスクリプトのための共通のバグと警告を識別します。大小を問わず、すべてのスクリプトに推奨されます。
$(command)を、バックティックの代わりに使用します。
ネストされたバックティックは、内側のバックティックを \ でエスケープする必要があります。$(command)形式はネストされたときにフォーマットが変更されませんし、読みやすいです。
例:
# This is preferred:
var="$(command "$(command1)")"
# This is not:
var="`command \`command1\``"
[ … ]、testと/usr/bin/[ よりも、[[ … ]] を優先します。
[[ … ]]は、 [[ と ]] の間でパス名の展開や単語の分割が行われないため、エラーが減少します。さらに、[[ … ]] は正規表現のマッチングが可能ですが、[ … ] はできません。
# This ensures the string on the left is made up of characters in
# the alnum character class followed by the string name.
# Note that the RHS should not be quoted here.
if [[ "filename" =~ ^[[:alnum:]]+name ]]; then
echo "Match"
fi
# This matches the exact pattern "f*" (Does not match in this case)
if [[ "filename" == "f*" ]]; then
echo "Match"
fi
# This gives a "too many arguments" error as f* is expanded to the
# contents of the current directory
if [ "filename" == f* ]; then
echo "Match"
fi
厄介な詳細については、http://tiswww.case.edu/php/chet/bash/FAQのE14を参照してください。
可能な場合は、フィラー文字ではなく引用符を使用してください。
Bashは、テストで空の文字列を処理するのに十分賢いです。したがって、コードがはるかに読みやすいことを考えると、フィラー文字ではなく、空/非空の文字列または空の文字列のテストを使用します。
# Do this:
if [[ "${my_var}" == "some_string" ]]; then
do_something
fi
# -z (string length is zero) and -n (string length is not zero) are
# preferred over testing for an empty string
if [[ -z "${my_var}" ]]; then
do_something
fi
# This is OK (ensure quotes on the empty side), but not preferred:
if [[ "${my_var}" == "" ]]; then
do_something
fi
# Not this:
if [[ "${my_var}X" == "some_stringX" ]]; then
do_something
fi
テスト対象についての混乱を避けるために、-zまたは -n を明示的に使用してください 。
# Use this
if [[ -n "${my_var}" ]]; then
do_something
fi
# Instead of this
if [[ "${my_var}" ]]; then
do_something
fi
どちらも機能しますが、明確にするために、等しい判断は、 = でなく、 == を使用してください 。前者は [[ の使用を奨励し、後者は代入と混同される可能性があります。しかし、[[ … ]] の中で<と> を使用する際には注意が必要 です。それは、辞書式比較を行います。数値比較には(( … ))または-lt と -gt を使用します。
# Use this
if [[ "${my_var}" == "val" ]]; then
do_something
fi
if (( my_var > 3 )); then
do_something
fi
if [[ "${my_var}" -gt 3 ]]; then
do_something
fi
# Instead of this
if [[ "${my_var}" = "val" ]]; then
do_something
fi
# Probably unintended lexicographical comparison.
if [[ "${my_var}" > 3 ]]; then
# True for 4, false for 22.
do_something
fi
ファイル名のワイルドカード展開を行うときは、明示的なパスを使用してください。
ファイル名は - で始めることができるため、*の代わりに ./* でワイルドカードを展開する方がはるかに安全です。
# Here's the contents of the directory:
# -f -r somedir somefile
# Incorrectly deletes almost everything in the directory by force
psa@bilby$ rm -v *
removed directory: `somedir'
removed `somefile'
# As opposed to:
psa@bilby$ rm -v ./*
removed `./-f'
removed `./-r'
rm: cannot remove `./somedir': Is a directory
removed `./somefile'
eval は避けるべきです。
Evalは、変数への代入に使用されるときに入力を変更し、それらの変数が何であったかを確認できないように変数を設定できます。
# What does this set?
# Did it succeed? In part or whole?
eval $(set_my_variables)
# What happens if one of the returned values has a space in it?
variable="$(eval some_function)"
引用の複雑さを避けるために、要素のリストを格納するためにBash配列を使用する必要があります。これは特に引数リストに当てはまります。より複雑なデータ構造を容易にするために配列を使用しないでください(上記のシェルを使用する場合を参照 )。
配列は文字列の順序付けられたコレクションを格納し、コマンドまたはループの個々の要素に安全に展開できます。
複数のコマンド引数に単一の文字列を使用することは避けてください。その結果必然的に、作成者 は eval を使用したり、文字列内で引用符をネストしようとするため、信頼できるまたは読みやすい結果が得られず、不必要な複雑さが発生します。
# An array is assigned using parentheses, and can be appended to
# with +=( … ).
declare -a flags
flags=(--foo --bar='baz')
flags+=(--greeting="Hello ${name}")
mybinary "${flags[@]}"
# Don’t use strings for sequences.
flags='--foo --bar=baz'
flags+=' --greeting="Hello world"' # This won’t work as intended.
mybinary ${flags}
# Command expansions return single strings, not arrays. Avoid
# unquoted expansion in array assignments because it won’t
# work correctly if the command output contains special
# characters or whitespace.
# This expands the listing output into a string, then does special keyword
# expansion, and then whitespace splitting. Only then is it turned into a
# list of words. The ls command may also change behavior based on the user's
# active environment!
declare -a files=($(ls /directory))
# The get_arguments writes everything to STDOUT, but then goes through the
# same expansion process above before turning into a list of arguments.
mybinary $(get_arguments)
配列の長所
配列を使用すると、引用セマンティクスを混乱させることなく、物事のリストを作成できます。逆に、配列を使用しないと、文字列内に引用符をネストしようとする誤った試みにつながります。
配列を使用すると、空白を含む文字列を含む、任意の文字列のシーケンス/リストを安全に格納できます。
配列の短所
配列を使用すると、スクリプトの複雑さが増すリスクがあります。
アレイの決定
リストを安全に作成して渡すには、配列を使用する必要があります。特に、コマンド引数のセットを作成するときは、引用の問題を混乱させないように配列を使用してください。引用符で囲まれた展開– "${array[@]}"–を使用して配列にアクセスします。ただし、より高度なデータ操作が必要な場合は、シェルスクリプトを完全に回避する必要があります。上記を参照してください。
whileへのパイピングよりも、プロセス置換または組み込みreadarray(bash4 +)を使用します。パイプはサブシェルを作成するため、パイプライン内で変更された変数は親シェルに伝播されません。
while へのパイプ内の暗黙のサブシェルは、追跡が難しい微妙なバグを引き起こす可能性があります。
last_line='NULL'
your_command | while read -r line; do
if [[ -n "${line}" ]]; then
last_line="${line}"
fi
done
# This will always output 'NULL'!
echo "${last_line}"
プロセス置換を使用しても、サブシェルが作成されます。ただし、while(または他の任意のコマンド)をサブシェルに配置せずに、サブシェルからwhileにリダイレクトすることができます。
last_line='NULL'
while read line; do
if [[ -n "${line}" ]]; then
last_line="${line}"
fi
done < <(your_command)
# This will output the last non-empty line from your_command
echo "${last_line}"
または、readarray組み込みを使用してファイルを配列に読み込み、配列の内容をループします。(上記と同じ理由で)readarrayでは、パイプではなくプロセス置換を使用する必要があることに注意してください。ただし、ループの入力生成は、後ではなく前に配置されるという利点があります。
last_line='NULL'
readarray -t lines < <(your_command)
for line in "${lines[@]}"; do
if [[ -n "${line}" ]]; then
last_line="${line}"
fi
done
echo "${last_line}"
注:出力は行ではなく空白で分割されるため、for var in $( …)のようにforループを使用して出力を反復処理する場合は注意が必要です。出力に予期しない空白を含めることがありえないため、これが安全であることがわかる場合がありますが、これが明確でないか、読みやすさを向上させない場合($(...)内部の長いコマンドなど)、while readループ、またはreadarray が、多くの場合、より安全で明確です。
let, $[ …], expr ではなく (( … )) あるいは $(( … ))を常に使用してください。
$[ … ]構文、expr コマンド、またはlet組み込みを使用しないでください。
< と > は、 [[ …]] 式内で数値比較を実行しません(代わりに辞書式比較を実行します。文字列のテストを参照してください)。できれば、数値比較のために[[ … ]] は一切使用しないで、代わりに (( … )) を使用してください。
(( … ))をスタンドアロンステートメントとして使うのは避け、使う場合は式がゼロと評価されることに注意することをお勧めします。
特にset -e が有効になっている場合。たとえば set -e; i=0; (( i++ )) は、シェルを終了させます。
# Simple calculation used as text - note the use of $(( … )) within
# a string.
echo "$(( 2 + 2 )) is 4"
# When performing arithmetic comparisons for testing
if (( a < b )); then
…
fi
# Some calculation assigned to a variable.
(( i = 10 * j + 400 ))
# This form is non-portable and deprecated
i=$[2 * 10]
# Despite appearances, 'let' isn't one of the declarative keywords,
# so unquoted assignments are subject to globbing wordsplitting.
# For the sake of simplicity, avoid 'let' and use (( … ))
let i="2 + 2"
# The expr utility is an external program and not a shell builtin.
i=$( expr 4 + 4 )
# Quoting can be error prone when using expr too.
i=$( expr 4 '*' 4 )
文体的な考慮事項はさておき、シェルの組み込み演算はexprよりも何倍も高速です。
変数を使用する場合、${var}(および$var)形式は$(( … ))内に必要ありません。シェルはあなたの代わりに var を検索することを知っており、${…}を省略してクリーンなコードになります。これは、常に中括弧を使用するという以前のルールとは少し反対であるため、これは推奨事項にすぎません。
# N.B.: Remember to declare your variables as integers when
# possible, and to prefer local variables over globals.
local -i hundred=$(( 10 * 10 ))
declare -i five=$(( 10 / 2 ))
# Increment the variable "i" by three.
# Note that:
# - We do not write ${i} or $i.
# - We put a space after the (( and before the )).
(( i += 3 ))
# To decrement the variable "i" by five:
(( i -= 5 ))
# Do some complicated computations.
# Note that normal arithmetic operator precedence is observed.
hr=2
min=5
sec=30
echo $(( hr * 3600 + min * 60 + sec )) # prints 7530 as expected
小文字、単語を区切るためのアンダースコア付き。ライブラリは::で区切ります 。関数名の後に括弧が必要です。キーワードfunctionはオプションですが、プロジェクト全体で一貫して使用する必要があります。
単一の関数を作成する場合は、小文字を使い、単語をアンダースコアで区切ります。パッケージを作成している場合は、パッケージ名を::で区切ります。中かっこは(Googleの他の言語と同様に)関数名と同じ行にあり、関数名と括弧の間にスペースを入れないでください。
# Single function
my_func() {
…
}
# Part of a package
mypackage::my_func() {
…
}
functionキーワードは「()」が関数名の後に存在しているときは余分ですが、関数の迅速な識別を強化します。
関数名と同じ。
ループの変数名は、ループする変数の名前と同じように命名する必要があります。
for zone in "${zones[@]}"; do
something_with "${zone}"
done
アンダースコアで区切られたすべて大文字。ファイルの先頭で宣言される。
定数および環境にエクスポートされるものはすべて大文字にする必要があります。
# Constant
readonly PATH_TO_FILES='/some/path'
# Both constant and environment
declare -xr ORACLE_SID='PROD'
最初の設定で定数になるものもあります(たとえば、getoptsを使用)。したがって、getoptsで、または条件に基づいて定数を設定することは問題ありませんが、直後に読み取り専用にする必要があります。わかりやすくするために、同等のdeclareコマンドの代わりに、readonlyまたはexportが推奨されます。
VERBOSE='false'
while getopts 'v' flag; do
case "${flag}" in
v) VERBOSE='true' ;;
esac
done
readonly VERBOSE
小文字。必要に応じて単語を区切るためにアンダースコアを付けます。
これは、Googleの他のコードスタイルとの一貫性を保つためです: maketemplateまたはmake_templateだが、make-templateではありません 。
readonlyまたはdeclare -rを使用して、読み取り専用であることを保証します。
グローバルはシェルで広く使用されているため、グローバルを操作するときにエラーをキャッチすることが重要です。読み取り専用を意図した変数を宣言するときは、これを明示的にしてください。
zip_version="$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)"
if [[ -z "${zip_version}" ]]; then
error_message
else
readonly zip_version
fi
関数固有の変数をlocal で宣言します。宣言と代入は別の行で行う必要があります。
ローカル変数を宣言するときにlocal を使用して、関数とその子の内部でのみローカル変数が見えるようにします。これにより、グローバルネームスペースが汚染されたり、関数の外部で重要になる可能性のある変数が誤って設定されたりすることが回避されます。
割り当て値がコマンド置換によって提供される場合、宣言と割り当ては別々のステートメントである必要があります。 local組み込みは、コマンド置換の終了コードを伝播しないため。
my_func2() {
local name="$1"
# Separate lines for declaration and assignment:
local my_var
my_var="$(my_func)"
(( $? == 0 )) || return
…
}
my_func2() {
# DO NOT do this:
# $? will always be zero, as it contains the exit code of 'local', not my_func
local my_var="$(my_func)"
(( $? == 0 )) || return
…
}
定数のすぐ下に、ファイル内のすべての関数をまとめます。関数間で実行可能コードを隠さないでください。これを行うと、コードを追跡するのが難しくなり、デバッグ時に厄介な驚きが生じます。
関数がある場合は、ファイルの先頭近くにそれらをすべてまとめます。関数を宣言する前に実行できるのは、includes、setステートメント、および定数の設定のみです。
main と呼ばれる関数が、少なくとも1つの他の関数を含むのに十分な長さのスクリプトに必要です。
プログラムの開始を簡単に見つけるために、最下部のmain関数と呼ばれる関数にメインプログラムを配置します。これにより、残りのコードベースとの一貫性が提供されるだけでなく、より多くの変数をlocal定義(メインコードが関数でない場合はできません)できるようになります。ファイルの最後の非コメント行は、次の main呼び出しである必要があります。
main "$@"
明らかに、それが単なる線形フローである短いスクリプトの場合、 mainはやり過ぎなので必要ありません。
常に戻り値を確認し、有益な戻り値を提供してください。
パイプされていないコマンドの場合は$?を使用するか、ifステートメントを介して直接チェックして、 単純にします。
例:
if ! mv "${file_list[@]}" "${dest_dir}/"; then
echo "Unable to move ${file_list[*]} to ${dest_dir}" >&2
exit 1
fi
# Or
mv "${file_list[@]}" "${dest_dir}/"
if (( $? != 0 )); then
echo "Unable to move ${file_list[*]} to ${dest_dir}" >&2
exit 1
fi
Bashは、パイプのすべての部分からの戻りコードをチェックできるPIPESTATUS変数もあります。パイプ全体の成功または失敗をチェックするだけでよい場合は、次のことが許容されます。
tar -cf - ./* | ( cd "${dir}" && tar -xf - )
if (( PIPESTATUS[0] != 0 || PIPESTATUS[1] != 0 )); then
echo "Unable to tar files to ${dir}" >&2
fi
ただし、PIPESTATUSは他のコマンドを実行するとすぐに上書きされるため、パイプ内のどこで発生したかに基づいてエラーに対して異なる動作をする必要がある場合はPIPESTATUSを、コマンドを実行した直後に別の変数に割り当てる必要があります ([ がコマンドでであり、PIPESTATUSを一掃することを忘れないでください)。
tar -cf - ./* | ( cd "${DIR}" && tar -xf - )
return_codes=( "${PIPESTATUS[@]}" )
if (( return_codes[0] != 0 )); then
do_something
fi
if (( return_codes[1] != 0 )); then
do_something_else
fi
シェルビルトインを呼び出すか、別のプロセスを呼び出すかを選択できる場合は、ビルトインを選択します。
bash(1)のパラメータ展開 関数などの組み込み関数を使用することをお勧めします。これは、より堅牢で移植性が高いためです(特にsedのようなものと比較した場合)。
例:
# Prefer this:
addition=$(( X + Y ))
substitution="${string/#foo/bar}"
# Instead of this:
addition="$(expr "${X}" + "${Y}")"
substitution="$(echo "${string}" | sed -e 's/^foo/bar/')"
常識を使用し、一貫性を保ちなさい。
C ++ガイドの下部にある別れの言葉セクションを数分かけて読んでください。
リビジョン2.02
以上