当前位置:   article > 正文

谷歌Shell编程指南,整洁代码风格,代码规范整理参考大全_google codeshell

google codeshell

目录

Shell风格指南

1.目录

2.背景

2.1 使用哪个Shell

2.2 何时使用Shell

3.Shell 文件和解释器调用

3.1 文件扩展名

3.2 SUID/SGID

4.环境

4.1 标准输出与标准错误

5.注释

5.1 文件头

5.2 函数注释

5.3 实施意见

5.4 待办事项注释

6.格式化

6.1 缩进

6.2 行长度和长字符串

6.3 管道

6.4 循环

6.5 Case语句

6.6 变量扩展

6.7 引用

7.特点和缺陷

7.1 外壳检查

7.2 命令替换

7.3 test, [ … ], 和[[ … ]]

7.4 判断字符串

7.5 文件名的通配符扩展

7.6 评估eval

7.7 数组

7.7.1 数组优点

7.7.2 数组的缺点

7.7.3 数组决策

7.8 通过管道传输到 While

7.9 算术

8.命名约定

8.1 函数名称

8.2 变量名称

8.3 常量和环境变量名称

8.4 源文件名

8.5 只读变量

8.6 使用局部变量

8.7 函数放置位置

8.8 main函数

9.调用命令

9.1 检查返回值

9.2 内置命令与外部命令

结论


Shell风格指南

修订版2.02

由许多 Google 员工创作、修订和维护。

1.目录

2.背景

2.1 使用哪个Shell

Bash 是唯一允许执行可执行文件的 shell 脚本语言

可执行文件必须以#!/bin/bash最少数量的标志开头。用于set设置 shell 选项,以便调用脚本时bash script_name 不会破坏其功能。

将所有可执行 shell 脚本限制为bash为我们提供了安装在所有计算机上的一致 shell 语言。

唯一的例外是,无论你的编码目的是什么,你都被迫这样做。其中一个例子是 Solaris SVR4 软件包,它的任何脚本都需要普通的 Bourne shell。

2.2 何时使用Shell

Shell 只能用于小型实用程序或简单的包装脚本。

虽然 shell 脚本不是一种开发语言,但它在整个 Google 中用于编写各种实用程序脚本。该风格指南更多的是对其用途的认可,而不是建议将其用于广泛部署。

一些指导方针:

  • 如果您主要调用其他实用程序并且执行相对较少的数据操作,那么 shell 是该任务的可接受选择。

  • 如果性能很重要,请使用 shell 以外的其他东西。

  • 如果您正在编写超过 100 行的脚本,或者使用非直接的控制流逻辑,那么您现在应该用更结构化的语言重写它。请记住,脚本会不断增长。尽早重写脚本,以避免日后进行更耗时的重写。

  • 在评估代码的复杂性时(例如,决定是否切换语言),请考虑该代码是否易于由其作者以外的人维护。

3.Shell 文件和解释器调用

3.1 文件扩展名

可执行文件应该没有扩展名(强烈推荐)或 .sh扩展名。lib库必须有.sh 扩展名并且不应该是可执行的。

执行程序时无需知道程序是用什么语言编写的,而且 shell 不需要扩展名,因此我们不希望对可执行文件使用扩展名。

然而,对于库来说,了解它是什么语言很重要,有时需要有不同语言的类似库。这允许具有相同用途但不同语言的库文件具有相同的名称,但特定于语言的后缀除外。

3.2 SUID/SGID

shell 脚本中禁止使用SUID 和 SGID 。

shell 存在太多安全问题,几乎不可能保证足够的安全以允许 SUID/SGID。虽然 bash 确实使运行 SUID 变得困难,但在某些平台上它仍然是可能的,这就是我们明确禁止它的原因。

sudo如果需要,可用于提供提升的访问权限。

4.环境

4.1 标准输出与标准错误

所有错误消息都应发送至STDERR.

这使得更容易区分正常状态和实际问题。

建议使用打印错误消息以及其他状态信息的功能。

 err() {
   echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $*" >&2
 }
 ​
 if ! do_something; then
   err "Unable to do_something"
   exit 1
 fi

5.注释

5.1 文件头

每个文件的开头都包含其内容的描述。

每个文件都必须有一个顶级注释,包括其内容的简要概述。版权声明和作者信息是可选的。

例子:

 #!/bin/bash
 #
 #******************************************************************************************
 #Author:                QianSong
 #QQ:                    xxxxxxxxxx
 #Date:                  2022-09-13
 #FileName:             handshakes.sh
 #URL:                   https://github.com
 #Description:          The handshake wifi cap info script
 #Copyright (C):         QianSong 2022 All rights reserved
 #******************************************************************************************

5.2 函数注释

任何不明显且简短的函数都必须注释。库中的任何函数都必须注释,无论长度或复杂性如何。

其他人应该可以通过阅读注释(以及自助,如果提供)来学习如何使用您的程序或使用库中的函数,而无需阅读代码。

所有函数注释都应使用以下方式描述预期的 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"
 }

5.3 实施意见

注释代码中棘手的、不明显的、有趣的或重要的部分。

这遵循一般的 Google 编码注释实践。不要注释一切。如果有复杂的算法或者您正在做一些不寻常的事情,请添加简短的注释。

5.4 待办事项注释

对临时代码、短期解决方案或足够好但不完美的代码使用 TODO 注释。

这符合C++ 指南中的约定。

TODOs 应包含全部大写的字符串TODO,后跟姓名、电子邮件地址或具有有关 所引用问题的最佳上下文的人员的其他标识符TODO。主要目的是保持一致 TODO,可以搜索以了解如何根据请求获取更多详细信息。ATODO并不承诺所提到的人会解决问题。因此,当您创建 时 TODO,几乎总是给出您的名字。

例子:

 # TODO(mrmonkey): Handle the unlikely edge cases (bug ####)

6.格式化

虽然您应该遵循正在修改的文件已有的样式,但任何新代码都需要以下内容。

6.1 缩进

缩进8个空格。没有tab键。

在代码块之间使用空行以提高可读性。缩进是两个空格。无论你做什么,都不要使用tab键。对于现有文件,请忠实于现有缩进。

6.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."

6.3 管道

如果管道不能全部容纳在一行上,则应将它们拆分为多行。

如果一条管道命令全部位于一行上,则它应该位于一行上。

如果不是,则应将其拆分为每行一个管道段,管道位于换行符上,并为管道的下一部分缩进 2 个空格。这适用于使用 组合的命令链|以及使用||&& 的逻辑符号。

 # All fits on one line
 command1 | command2
 ​
 # Long commands
 command1 \
   | command2 \
   | command3 \
   | command4

6.4 循环

; dowhilefor,或if; then放在同一行 。

shell 中的循环有点不同,但我们在声明函数时遵循与大括号相同的原则。即:; then; do应该与 if/for/while 位于同一行。 elsefidone等等结束语应独占一行,结束语应与开头语垂直对齐。

例子:

 # 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

6.5 Case语句

  • 将可选项缩进 8 个空格。

  • 单行替代方案需要在模式的右括号之后和;; 之前有一个空格。

  • 长或多命令替代方案应与模式、操作一起分成多行,并把;;放在单独的行上。

case匹配表达式从和缩进一级esac。多行操作缩进另一级。一般来说,不需要引用匹配表达式。模式表达式前面不应有左括号。避免使用;&;;&符号。

 case "${expression}" in
   a)
     variable="…"
     some_command "${variable}" "${other_expr}" …
     ;;
   absolute)
     actions="relative"
     another_command "${actions}" "${other_expr}" …
     ;;
   *)
     error "Unexpected expression '${expression}'"
     ;;
 esac

简单的命令可以与模式;;放在同一行 只要表达式保持可读即可。这通常适用于单字母选项处理。当操作无法放在一行上时,请将模式单独放在一行上,然后是操作,然后;;也单独放在一行上。当与操作位于同一行时,请在模式的右括号后面使用一个空格,在 ;;之前使用另一个空格。

 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

6.6 变量扩展

按优先顺序:与您发现的内容保持一致;引用你的变量;更喜欢是这样引用:"${var}",而不是像这样:"$var".

这些是强烈推荐的指南,但不是强制性规定。尽管如此,它是一项建议而非强制性的事实并不意味着应该轻视或淡化它。

它们按优先顺序列出。

  • 与您在现有代码中找到的内容保持一致。

  • 引用变量,请参阅下面的引用部分

  • 不要用大括号分隔单字符 shell 特殊字符/位置参数,除非绝对必要或避免深度混淆。

  • 最好用大括号分隔所有其他变量。

    这种是推荐的

     # 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}不是一种引用形式。必须使用“双引号” 。

6.7 引用

  • 始终引用包含变量、命令替换、空格或 shell 元字符的字符串,除非需要小心的未加引号的扩展或者它是 shell 内部整数(请参阅下一点)。

  • 使用数组来安全引用元素列表,尤其是命令行标志。请参阅下面的数组

  • 可以选择引用 shell 内部的只读特殊变量,这些变量被定义为整数:$?, $#, $$, $!(man bash)。为了保持一致性,更喜欢引用“命名”内部整数变量,例如 PPID 等。

  • 更喜欢引用“单词”字符串(而不是命令选项或路径名)。

  • 切勿引用文字整数。

  • 请注意[[ … ]] 中模式匹配的引用规则。请参阅 下面的[test[ … \][[ … ]]](styleguide | Style guides for Google-originated open-source projects)部分。

  • "$@"除非您有特定原因使用$*,例如仅将参数附加到消息或日志中的字符串,否则请使用。

 # '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 "$#, $@")

7.特点和缺陷

7.1 外壳检查

ShellCheck项目可识别 shell 脚本的常见错误和警告。建议用于所有脚本,无论大小。

7.2 命令替换

使用$(command)代替反引号``来进行命令替换。

嵌套反引号需要使用\ 转义内部反引号。嵌套$(command)时格式不会改变并且更易于阅读。

例子:

 # This is preferred:
 var="$(command "$(command1)")"
 # This is not:
 var="`command \`command1\``"

7.3 test, [ … ], 和[[ … ]]

[[ … ]]优于[ … ],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

有关详细信息,请参阅 E14:http://tiswww.case.edu/php/chet/bash/FAQ

7.4 判断字符串

尽可能使用引号而不是填充字符。

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

为了清楚起见,请使用==平等而不是即使 =两者都有效。前者鼓励使用 [[,后者可能与作业混淆。<但是,在使用and > in[[ … ]]其中执行字典顺序比较时要小心。使用(( … ))或者-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

7.5 文件名的通配符扩展

进行文件名通配符扩展时使用显式路径。

由于文件名可以以./* 开头,因此使用*而不是-扩展通配符要安全得多。

 # 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'

7.6 评估eval

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)"

7.7 数组

Bash 数组应该用于存储元素列表,以避免引用复杂化。这尤其适用于参数列表。不应使用数组来实现更复杂的数据结构(请参阅 上面的何时使用 Shell)。

数组存储有序的字符串集合,并且可以安全地扩展到命令或循环的单个元素。

应避免对多个命令参数使用单个字符串,因为它不可避免地导致作者使用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)

7.7.1 数组优点
  • 使用数组可以列出事物,而不会混淆引用语义。相反,不使用数组会导致错误地尝试在字符串中嵌套引用。

  • 数组可以安全地存储任意字符串的序列/列表,包括包含空格的字符串。

7.7.2 数组的缺点

使用数组可能会增加脚本复杂性的风险。

7.7.3 数组决策

数组应该用于安全地创建和传递列表。特别是,在构建一组命令参数时,请使用数组以避免混淆引用问题。使用带引号的扩展 - "${array[@]}"- 来访问数组。然而,如果需要更高级的数据操作,则应完全避免 shell 脚本;见上文

7.8 通过管道传输到 While

使用进程替换或readarray内置 (bash4+) 优先于管道到while. 管道创建一个子 shell,因此管道内修改的任何变量都不会传播到父 shell。

管道中的隐式子 shell 可能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}"

使用进程替换也会创建一个子 shell。但是,它允许从子 shell 重定向到,而while无需将while(或任何其他命令) 放入子 shell 中。

 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 循环迭代输出时要小心,如 中所示for var in $(...),因为输出是按空格而不是按行分割的。有时您会知道这是安全的,因为输出不能包含任何意外的空格,但如果这不明显或不能提高可读性(例如内部的长命令$(...)),while read则循环readarray通常更安全、更清晰。

7.9 算术

始终使用(( … ))or$(( … ))而不是 letor$[ … ]expr

切勿使用$[ … ]语法、expr 命令或let内置命令。

<并且>不在[[ … ]]表达式内执行数值比较(而是执行字典比较;请参阅测试字符串)。[[ … ]] 出于偏好,根本不要使用数字比较, (( … ))而是使用。

建议避免用作(( … ))独立语句,否则要警惕其表达式计算为零

  • 特别是set -e启用后。例如, set -e; i=0; (( i++ ))将导致 shell 退出。

 # 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 )

撇开风格上的考虑不谈,对比shell 的内置算术比如expr.

使用变量$var时,$(( … ))中不需要${var}这样的花括号变量的形式。shell 知道并且会查找您的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

8.命名约定

8.1 函数名称

小写,用下划线分隔单词。用 ::分开库 。函数名称后面需要括号。该关键字function是可选的,但必须在整个项目中一致使用。

如果您正在编写单个函数,请使用小写字母并使用下划线分隔单词。如果您正在编写一个包,请用:: 分隔包名称。大括号必须与函数名称在同一行(与 Google 的其他语言一样),并且函数名称和括号之间不能有空格。

 # Single function
 my_func() {
   …
 }
 ​
 # Part of a package
 mypackage::my_func() {
   …
 }

function当函数名称后面出现“()”时,该关键字是无关的,但可以增强函数的快速识别。

8.2 变量名称

至于函数名。

循环的变量名称应该与您循环的任何变量的名称类似。

 for zone in "${zones[@]}"; do
   something_with "${zone}"
 done

8.3 常量和环境变量名称

所有大写,以下划线分隔,在文件顶部声明。

常量和任何导出到环境的东西都应该大写。

 # Constant
 readonly PATH_TO_FILES='/some/path'
 ​
 # Both constant and environment
 declare -xr ORACLE_SID='PROD'

有些事情在第一次设置时就变得不变(例如,通过 getopts)。因此,可以在 getopts 中exportdeclare基于条件设置常量,但之后应立即将其设置为只读。为了清楚起见,建议使用readonlyor来代替等效命令。

 VERBOSE='false'
 while getopts 'v' flag; do
   case "${flag}" in
     v) VERBOSE='true' ;;
   esac
 done
 readonly VERBOSE

8.4 源文件名

小写,如果需要,可以用下划线分隔单词。

这是为了与 Google 中的其他代码样式保持一致: maketemplateormake_template但不是 make-template

8.5 只读变量

使用readonlydeclare -r确保它们是只读的。

由于全局变量在 shell 中广泛使用,因此在使用它们时捕获错误非常重要。当您声明一个只读变量时,请明确说明。

 zip_version="$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)"
 if [[ -z "${zip_version}" ]]; then
   error_message
 else
   readonly zip_version
 fi

8.6 使用局部变量

使用 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
 ​
   …
 }

8.7 函数放置位置

将所有函数放在常量下面的文件中。不要隐藏函数之间的可执行代码。这样做会使代码难以理解,并在调试时导致令人讨厌的意外。

如果您有函数,请将它们全部放在文件顶部附近。set在声明函数之前只能执行包含、语句和设置常量。

8.8 main函数

对于足够长的脚本,需要调用一个main函数来包含至少一个其他函数。

为了方便找到程序的开头,将主程序放在一个称为main最底层函数的函数中。这提供了与代码库其余部分的一致性,并允许您定义更多变量local(如果主代码不是函数,则无法完成)。文件中的最后一个非注释行应该是对以下内容的main函数调用:

 main "$@"

显然,对于只是线性流程的短脚本来说,这 main是多余的,因此不是必需的。

9.调用命令

9.1 检查返回值

始终检查返回值并提供信息丰富的返回值。

对于非管道命令,请$?直接通过 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

9.2 内置命令与外部命令

如果要在调用 shell 内置函数和调用单独的进程之间进行选择,请选择内置函数。

我们更喜欢使用内置函数,例如参数扩展 函数,因为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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/386550
推荐阅读
相关标签
  

闽ICP备14008679号