【翻译】谷歌shell编程规范
-
使用哪一种shell解释器。 bash应该是一唯一使用的脚本解释器,除此之外,你不应该使用其他的解释器的特性,除非你真的需要,或者是条件限制。确保你的脚本在移植到其他机器上也能够正常执行。bash脚本应该都包含一个shanbang,
#!/bin/bash
。 -
什么时候应该是shell。 shell只应该被用来做一些很小的工具,或者是一些脚本的包装。 虽然shell不知一个正规的编程语言,但是google内部还是有很多地方在使用它。为了避免滥用,你应该准守如下规则:
- 如果只是调用一些其他的工具,或者只是简单的处理输出,可以使用shell。
- 如果你在意执行速度,那么不要使用shell,使用其他语言。
- 如果你发现除了使用常规变量之外,你还需要使用数组,就像
${PIPESTATUS}
,那么你应该使用python。 - 如果你发现你的脚本长度超过了100行代码,那么你应该使用python来重写他,随着代码长度的增加,你应该尽快重构你的代码。
-
应该使用哪种后缀名。 我们强烈的建议你不要使用后缀名或者使用
.sh
作为后缀名。如果是最为依赖库,那么应该用.sh
作为后缀名,并且不应该有执行权限。当我们执行一个脚本的时候,不需要知道他是哪种语言编写,并且shell也并没有shell也没有要求使用后缀名。然而,作为库代码,应该表明是使用哪种语言编写的,应该使用后缀来表明他的实现语言,比如.bash
。 -
SUID和SGID。 shell脚本禁止使用SUID和SGID功能。 因为有太多的安全事故,所以必须禁止shell使用SUID和SGID,虽然在大部分平台上是难以开启这项功能的,但是某些平台上还是可以的,所以必须要禁止他,如果要获取权限,应该使用
sudo
。 -
标准输出与标准错误输出。 所有的错误都应该被打印到标准错误输出
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
-
文件头。 每个脚本的开头都必须有一段注释来描述这个脚本的内容。
-
函数注释。 不管函数的长度是多少,都应该添加注释。对于所有的库函数,都应该有注释描述。任何在使用库函数的时候,都应该能够通过注释的描述来使用这个库函数,或者是使用函数提供的
--help
选项(如果提供了的话)。函数的注释应该包含如下信息:
- 函数的描述
- 全局变量的使用和修改
- 函数的参数说明
- 函数的返回值和退出状态说明 例如:
#!/bin/bash
#
# Perform hot backups of Oracle databases.
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() {
...
}
-
TODO说明。 对于临时的代码,或者是写的不好有待完善的代码,应该使用TODO说明。
-
代码格式。 当你添加新的代码,或者修改代码的时候,应该与之前的编码风格保持一致。
-
缩进。 使用两个空格作为缩进,不要使用tab。
-
字符串长度。 字符串的最大长度不要超过80个字符,如果超过了,请使用here docuemtn或者用shell的内置换行功能,强烈建议你找一个能够是字符串变短的方法。 例如:
# 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."
- 管道符号。 建议不讲所有管道符号写在同一行,如果有多个管道符号,应该写在多行,管道符号开头,并且使用两个空格作为缩颈。
例如:
# All fits on one line
command1 | command2
# Long commands
command1 \
| command2 \
| command3 \
| command4
- 循环。
将
;
符号和do
,then
,for
,if
,while
等关键词放在同一行。在shell中循环有一些不一样,但是我们应该遵循相关的准备:;
和then
和do
应该放在同一行,else
放在单独一行。 例如:
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 [[ "$?" -ne 0 ]]; then
error_message
fi
else
mkdir -p "${dir}/${ORACLE_SID}"
if [[ "$?" -ne 0 ]]; then
error_message
fi
fi
done
- case语句。
- 使用2个字符作为缩进。
- 在每个case语句结束的最后,单独一样使用
;;
作为结束。 - 比较长的语句应该被分成多行。
条件匹配应该在
case
的esac
的基础上有一个缩进。满足条件的语句应该在当前shell中执行,不要使用一个子shell或者&
。 例如:
case "${expression}" in
a)
variable="..."
some_command "${variable}" "${other_expr}" ...
;;
absolute)
actions="relative"
another_command "${actions}" "${other_expr}" ...
;;
*)
error "Unexpected expression '${expression}'"
;;
esac
一些简单的语句可以和条件语句和;;
放在同一行,但是一定要保准便于阅读。一定要语句结束之后,;;
之前使用一个空格作为分隔符。
- 变量取值。 在保准和旧代码风格一致的前提条件下,优先使用"$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 f; do
echo "file=${f}"
done < <(ls -l /tmp)
# Section of discouraged cases
# Unquoted vars, unbraced vars, brace-quoted 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"
15. 引号。
* 永远要使用引号将字符串包裹起来。
* 不要使用引号包裹一个数字。
16. 子shell。
使用`$(command)`代码``command``,``commander``在嵌套的时候需要使用反引号来转义,而括号则不需要。
17. test。
推荐使用 `[[ ]]` , 而不是 `[ test str ]`
例如:
```shell
# 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.
# For the gory details, see
# E14 at https://tiswww.case.edu/php/chet/bash/FAQ
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
- 字符串判断。 使用双引号,而不是字符串过滤,shell对字符串支持非常不友好,应该尽量保准代码简单,便于阅读。 例如:
# 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 as errors can occur if ${my_var} expands to a test
# flag
if [[ "${my_var}" ]]; then
do_something
fi
- 文件扩展名。
因为文件名可以使用
-
开头,所以应该使用./*
来替代*
。
# Here's the contents of the directory:
# -f -r somedir somefile
# This 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。 这个功能应该被禁止。
-
命名。 应该使用小写字符加下划线的组合。对于循环中的变量,应该和你当前正在迭代的变量尽量相似。
-
常量和环境变量。 常量和环境变量应该在文件的开头生命,应该大写字母加写划线的组合命名方式。一些一次读取以后就不在变化的变量,也应该用大写字母加下划线的方式来命令,并且使用
readonly
来申明。 例如:
VERBOSE='false'
while getopts 'v' flag; do
case "${flag}" in
v) VERBOSE='true' ;;
esac
done
readonly VERBOSE
-
文件名。 文件名应该使用小写字母加下划线的组合方式。
-
read-only变量。 使用readonly和declare来保证变量只读。