推举(Preferable):用户理应采取,但如有分外情形,可以不采取;

必须(Mandatory):用户必须采取(除非是少数非常分外的情形,才能不采取);

注:未明确指明的则默认为必须(Mandatory)

php设置文本编码Linux Shell 剧本编程最佳实践 jQuery

紧张参考如下文档:

Google Shell Style Guide

Bash Hackers Wiki

源文件根本利用场景
仅建议Shell用作相对大略的实用工具或者包装脚本。
因此单个shell脚本内容不宜太过繁芜。
在选择何时利用shell脚本时时应遵照以下原则:
如紧张用于调用其他工具且需处理的数据量较少,则shell是一个选择
如对性能十分敏感,则更推举选择其他措辞,而非shell
如需处理相对繁芜的数据构造,则更推举选择其他措辞,而非shell
如脚本内容逐渐增长且有可能涌现连续增长的趋势,请尽早利用其他措辞重写
文件名
可实行文件不建议有扩展名,库文件必须利用.sh作为扩展名,且应是不可实行的。
实行一个程序时,无需知道其编写措辞,且shell脚本并不哀求具有扩展名,以是更方向可实行文件没有扩展名。
而库文件知道其编写措辞十分主要,利用.sh作为特定措辞后缀的扩展名,可以和其他措辞编写的库文件加以区分。
文件名哀求全部小写, 可以包含下划线_或连字符-, 建议可实行文件利用连字符,库文件利用下划线。

正例:

my-useful-binmy_useful_libraries.shmyusefullibraries.sh

反例:

My_Useful_BinmyUsefulLibraries.sh

文件编码
源文件编码格式为UTF-8。
避免不同操作系统对文件换行处理的办法不同,一律利用LF
单行长度
每行最多不超过120个字符。
每行代码最大长度限定的根本缘故原由是过长的行会导致阅读障碍,使得缩进失落效。
除了以下两种情形例外:
导入模块语句
注释中包含的URL
如涌现长度必须超过120个字符的字符串,应只管即便利用here document或者嵌入的换行符等得当的方法使其变短。

示例:

# DO use 'here document'scat <<END;I am an exceptionally longstring.END# Embedded newlines are ok toolong_string=\公众I am an exceptionally long string.\公众

空缺字符

除了在行结束利用换行符,空格是源文件中唯一许可涌现的空缺字符。

字符串中的非空格空缺字符,利用转义字符

不许可行前利用tab缩进,如果利用tab缩进,必须设置1个tab为4个空格

不应在行尾涌现没故意义的空缺字符

垃圾清理(推举)

对从来没有用到的或者被注释的方法、变量等要武断从代码中清理出去,避免过多垃圾造成滋扰。

构造利用bash

Bash 是唯一被许可利用的可实行脚本shell。

可实行文件必须以 #!/bin/bash开始。
请利用set来设置shell的选项,使得用bash <script_name>调用你的脚本时不会毁坏其功能。

限定所有的可实行shell脚本为bash使得我们安装在所有打算机中的shell措辞保持同等性。
正例:

#!/bin/bashset -e

反例:

#!/bin/sh -e

容许证或版权信息(推举)

容许证与版权信息需放在源文件的起始位置。
例如:

## Licensed under the BSD 3-Clause License (the \公众License\"大众); you may not use this file except# in compliance with the License. You may obtain a copy of the License at## https://opensource.org/licenses/BSD-3-Clause## Unless required by applicable law or agreed to in writing, software distributed# under the License is distributed on an \"大众AS IS\公众 BASIS, WITHOUT WARRANTIES OR# CONDITIONS OF ANY KIND, either express or implied. See the License for the# specific language governing permissions and limitations under the License.#

缩进块缩进

每当开始一个新的块,缩进增加4个空格(不能利用\t字符来缩进)。
当块结束时,缩进返回先前的缩升级别。
缩升级别适用于代码和注释。

main() { # 缩进4个空格 say=\"大众hello\"大众 flag=0 if [[ $flag = 0 ]]; then # 缩进4个空格 echo \"大众$say\公众 fi

管道

如果一行容不下全体管道操作,那么请将全体管道操作分割成每行一个管段。
如果一行容得下全体管道操作,那么请将全体管道操作写在同一行,管道旁边应有空格。
否则,该当将全体管道操作分割成每行一段,管道操作的下一部分该当将管道符放在新行并且缩进4个空格。
这适用于管道符 | 以及逻辑运算 ||&&
正例:

# 单行管道连接,管道旁边空格command1 | command2# 长命令管道换行连接,管道放置于下一个命令开头,缩进4个空格command1 \ | command2 \ | command3 \ | command4

反例:

# 管道旁边无空格command1|command2# 换行连接管道放置于行末command1 | \ command2 | \ command3 | \ command4

循环

请将 ; do , ; then 和 while , for , if 放在同一行。

shell中的循环略有不同,但是我们遵照跟声明函数时的大括号相同的原则。
即:; do , ; then 该当和 while/for/if 放在同一行。
else 该当单独一行。
结束语句该当单独一行且跟开始语句缩进对齐。

正例:

for dir in ${dirs_to_cleanup}; do if [[ -d \"大众${dir}/${BACKUP_SID}\"大众 ]]; then log_date \"大众Cleaning up old files in ${dir}/${BACKUP_SID}\公众 rm \"大众${dir}/${BACKUP_SID}/\"大众 if [[ \"大众$?\公众 -ne 0 ]]; then error_message fi else mkdir -p \公众${dir}/${BACKUP_SID}\"大众 if [[ \"大众$?\"大众 -ne 0 ]]; then error_message fi fidone

反例:

function getBatchName(){ batch_name=\"大众batch\"大众 if [[ \"大众$input5\"大众x == $batch_name ]] then batch_name=$input5 else if [[ \公众$input6\公众x == $batch_name ]] then batch_name=$input6 else if [[ \"大众$input7\"大众x == $batch_name ]] then batch_name=$input7 fi fi fi}

case语句

通过4个空格缩进可选项。
可选项中的多个命令该当被拆分成多行,模式表达式、操作和结束符 ;; 在不同的行。
匹配表达式比 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}\"大众 ;; esacdone

函数位置

将文件中所有的函数统一放在常量下面。
不要在函数之间隐蔽可实行代码。

如果你有函数,请将他们统一放在文件头部。
只有includes, set 声明和常量设置可能在函数声明之前完成。
不要在函数之间隐蔽可实行代码。
如果那样做,会使得代码在调试时难以跟踪并涌现意想不到的结果。

主函数main

对付包含至少了一个其他函数的足够长的脚本,建议定义一个名为 main 的函数。
对付功能大略的短脚本, main函数是没有必要的。

为了方便查找程序的入口位置,将主程序放入一个名为 main 的函数中,作为最底部的函数。
这使其和代码库的别的部分保持同等性,同时许可你定义更多变量为局部变量(如果主代码不是一个函数就不支持这种做法)。
文件中末了的非注释行该当是对 main 函数的调用:

main \"大众$@\公众

注释
代码注释的基本原则:
注释应能使代码更加明确
避免注释部分的过度润色
保持注释部分大略、明确
在编码以前就应开始写注释
注释应解释设计思路而不是描述代码的行为
注释与其周围的代码在同一缩升级别,#号与注释文本间需保持一个空格以和注释代码进行区分。
文件头
每个文件的开头是其文件内容的描述。
除版权声明外,每个文件必须包含一个顶层注释,对其功能进行简要概述。
例如:

#!/bin/bash## Perform hot backups of databases.

功能注释
主体脚本中除简洁明了的函数外都必须带有注释。
库文件中所有函数无论其是非和繁芜性都必须带有注释。
这使得其他人通过阅读注释即可学会如何利用你的程序或库函数,而不须要阅读代码。
所有的函数注释该当包含:
函数的描述
全局变量的利用和修正
利用的参数解释
返回值,而不是上一条命令运行后默认的退出状态

例如:

#!/bin/bash## Perform hot backups of databases.export PATH='/usr/sbin/bin:/usr/bin:/usr/local/bin'######################################## Cleanup files from the backup dir# Globals:# BACKUP_DIR# BACKUP_SID# Arguments:# None# Returns:# None#######################################cleanup() { ...}

TODO注释

对那些临时的, 短期的办理方案, 或已经够好但仍不完美的代码利用 TODO 注释.
TODO 注释要利用整年夜写的字符串 TODO, 在随后的圆括号里写上你的名字,邮件地址, bug ID, 或其它身份标识和与这一 TODO 干系的 issue。
紧张目的是让添加注释的人 (也是可以要求供应更多细节的人) 可根据规范的TODO 格式进行查找。
添加 TODO 注释并不虞味着你要自己来改动,因此当你加上带有姓名的 TODO 时, 一样平常都是写上自己的名字。
这与C++ Style Guide中的约定相同等。
例如:

# TODO(mrmonkey): Handle the unlikely edge cases (bug ####)# TODO(--bug=123456): remove the \"大众Last visitors\公众 feature

命名函数名
利用小写字母,并用下划线分隔单词。
利用双冒号::分隔包名。
函数名之后必须有圆括号。
如果你正在写单个函数,请用小写字母来命名,并用下划线分隔单词。
如果你正在写一个包,利用双冒号 :: 来分隔包名。
函数名和圆括号之间没有空格,大括号必须和函数名位于同一行。
当函数名后存在 () 时,关键词 function 是多余的,建议不带 function 的写法,但至少做到同一项目内风格保持同等。
正例:

# Single functionmy_func() { ...}# Part of a packagemypackage::my_func() { ...}

反例:

function my_func{ ...}

变量名

规则同函数名同等。

循环中的变量名该当和正在被循环的变量名保持相似的名称。
示例:

for zone in ${zones}; do something_with \公众${zone}\"大众done

常量和环境变量名

全部大写,用下划线分隔,声明在文件的顶部。

常量和任何导出到环境中的变量都该当大写。
示例:

# Constantreadonly PATH_TO_FILES='/some/path'# Both constant and environmentdeclare -xr BACKUP_SID='PROD'

有些情形下首次初始化及常量(例如,通过getopts),因此,在getopts中或基于条件来设定常量是可以的,但之后该当立即设置其为只读。
值得把稳的是,在函数中利用 declare 对全局变量无效,以是推举利用 readonly 和 export 来代替。
示例:

VERBOSE='false'while getopts 'v' flag; do case \"大众${flag}\"大众 in v) VERBOSE='true' ;; esacdonereadonly VERBOSE

只读变量

利用 readonly 或者 declare -r 来确保变量只读。

由于全局变量在shell中广泛利用,以是在利用它们的过程中捕获缺点是很主要的。
当你声明了一个变量,希望其只读,那么请明确指出。
示例:

zip_version=\"大众$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)\公众if [[ -z \公众${zip_version}\"大众 ]]; then error_messageelse readonly zip_versionfi

局部变量
每次只声明一个变量,不要利用组合声明,比如a=1 b=2;
利用 local 声明特定功能的变量。
声明和赋值该当在不同行。
必须利用 local 来声明局部变量,以确保其只在函数内部和子函数中可见。
这样可以避免污染全局名称空间以及避免无意中设置可能在函数外部具有主要意义的变量。
当利用命令更换进行赋值时,变量声明和赋值必须分开。
由于内建的 local 不会从命令更换中通报退出码。
正例:

my_func2() { local name=\公众$1\"大众 # 命令更换赋值,变量声明和赋值需放到不同行: local my_var my_var=\"大众$(my_func)\公众 || return ...}

反例:

my_func2() { # 禁止以下写法: $? 将获取到'local'指令的返回值, 而非 my_func local my_var=\"大众$(my_func)\公众 [[ $? -eq 0 ]] || return ...}

非常与日志非常

利用shell返回值来返回非常,并根据不同的非常情形返回不同的值。

日志

所有的缺点信息都应被导向到STDERR,这样将有利于涌现问题时快速区分正常输出和非常输出。

建议利用与以下函数类似的办法来打印正常和非常输出:

err() { echo \公众[$(date +'%FT%T%z')]: $@\"大众 >&2}if ! do_something; then err \公众Unable to do_something\"大众 exit \"大众${E_DID_NOTHING}\公众fi

编程实践(持续分类并完善)变量扩展(推举)

常日情形下推举为变量加上大括号如 \"大众${var}\公众 而不是 \公众$var\公众 ,但详细也要视情形而定。

以下按照优先顺序列出建议:

与现有代码保持同等

单字符变量在特定情形下才须要被括起来

利用引号引用变量,参考下一节:变量引用

详细示例如下:正例:

# 位置变量和分外变量,可以不用大括号:echo \"大众Positional: $1\公众 \"大众$5\公众 \"大众$3\公众echo \公众Specials: !=$!, -=$-, _=$_. ?=$?, #=$# =$ @=$@ \$=$$ ...\"大众# 本地位变量大于即是10,则必须有大括号:echo \"大众many parameters: ${10}\"大众# 当涌现歧义时,必须有大括号:# Output is \"大众a0b0c0\"大众set -- a b cecho \"大众${1}0${2}0${3}0\公众# 利用变量扩展赋值时,必须有大括号:DEFAULT_MEM=${DEFUALT_MEM:-\"大众-Xms2g -Xmx2g -XX:MaxDirectMemorySize=4g\"大众}# 其他常规变量的推举处理办法: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 cecho \"大众$10$20$30\"大众

变量引用(推举)

变量引用常日情形下应遵照以下原则:

默认情形下推举利用引号引用包含变量、命令更换符、空格或shell元字符的字符串

在有明确哀求必须利用无引号扩展的情形下,可不用引号

字符串为单词类型时才推举用引号,而非命令选项或者路径名

不要对整数利用引号

特殊把稳 [[ 中模式匹配的引号规则

在无分外情形下,推举利用 $@ 而非 $

以下通过示例解释:

# '单引号' 表示禁用变量更换# \"大众双引号\"大众 表示须要变量更换# 示例1:命令更换需利用双引号flag=\公众$(some_command and its args \公众$@\公众 'quoted separately')\"大众# 示例2:常规变量需利用双引号echo \"大众${flag}\"大众# 示例3:整数不该用引号value=32# 示例4:即便命令更换输出为整数,也须要利用引号number=\公众$(generate_number)\"大众# 示例5:单词可以利用引号,但不作逼迫哀求readonly USE_INTEGER='true'# 示例6:输出分外符号利用单引号或转义echo 'Hello stranger, and well met. Earn lots of $$$'echo \"大众Process $$: Done making \$\$\$.\"大众# 示例7:命令参数及路径不须要引号grep -li Hugo /dev/ \公众$1\公众# 示例8:常规变量用双引号,ccs可能为空的分外情形可不用引号git send-email --to \公众${reviewers}\公众 ${ccs:+\"大众--cc\"大众 \公众${ccs}\"大众}# 示例9:正则用单引号,$1可能为空的分外情形可不用引号grep -cP '([Ss]pecial|\|?characters)$' ${1:+\"大众$1\"大众}# 示例10:位置参数通报推举带引号的\"大众$@\公众,所有参数作为单字符串通报用带引号的\公众$\公众# content of t.shfunc_t() { echo num: $# echo args: 1:$1 2:$2 3:$3}func_t \公众$@\"大众func_t \"大众$\公众# 当实行 ./t.sh a b c 时输出如下:num: 3args: 1:a 2:b 3:cnum: 1args: 1:a b c 2: 3:

命令更换

利用 $(command) 而不是反引号。

因反引号如果要嵌套则哀求用反斜杠转义内部的反引号。
而 $(command) 形式的嵌套无需转义,且可读性更高。

正例:

var=\"大众$(command \"大众$(command1)\公众)\"大众

反例:

var=\"大众`command \`command1\``\"大众

条件测试

利用 [[ ... ]] ,而不是 [ , test , 和 /usr/bin/[

由于在 [[]] 之间不会涌现路径扩展或单词切分,以是利用 [[ ... ]] 能够减少犯错。
[[ ... ]] 支持正则表达式匹配,而 [ ... ] 不支持。
参考以下示例:

# 示例1:正则匹配,把稳右侧没有引号# 详尽细节参考:http://tiswww.case.edu/php/chet/bash/FAQ 中E14部分if [[ \公众filename\"大众 =~ ^[[:alnum:]]+name ]]; then echo \"大众Match\"大众fi# 示例2:严格匹配字符串\公众f\"大众(本例为不匹配)if [[ \公众filename\公众 == \"大众f\"大众 ]]; then echo \"大众Match\"大众fi# 示例3:[]中右侧不加引号将涌现路径扩展,如果当前目录下有f开头的多个文件将报错[: too many argumentsif [ \"大众filename\"大众 == f ]; then echo \"大众Match\"大众fi

字符串测试

尽可能利用变量引用,而非字符串过滤。

Bash可以很好的处理空字符串测试,请利用空/非空字符串测试方法,而不是过滤字符,让代码具有更高的可读性。
正例:

if [[ \"大众${my_var}\公众 = \公众some_string\"大众 ]]; then do_somethingfi

反例:

if [[ \公众${my_var}X\"大众 = \"大众some_stringX\"大众 ]]; then do_somethingfi

正例:

# 利用-z测试字符串为空if [[ -z \"大众${my_var}\公众 ]]; then do_somethingfi

反例:

# 利用空引号测试空字符串,能用但不推举if [[ \"大众${my_var}\公众 = \公众\公众 ]]; then do_somethingfi

正例:

# 利用-n测试非空字符串if [[ -n \公众${my_var}\"大众 ]]; then do_somethingfi

反例:

# 测试字符串非空,能用但不推举if [[ \"大众${my_var}\"大众 ]]; then do_somethingfi

文件名扩展

当进行文件名的通配符扩展时,请指定明确的路径。

当目录中有分外文件名如以 - 开头的文件时,利用带路径的扩展通配符 ./ 比不带路径的 要安全很多。

# 例如目录下有以下4个文件和子目录:# -f -r somedir somefile# 未指定路径的通配符扩展会把-r和-f当作rm的参数,逼迫删除文件:psa@bilby$ rm -v removed directory: `somedir'removed `somefile'# 而指定了路径的则不会:psa@bilby$ rm -v ./removed `./-f'removed `./-r'rm: cannot remove `./somedir': Is a directoryremoved `./somefile'

慎用eval

该当避免利用eval。

Eval在用于分配变量时会修正输入内容,但设置变量的同时并不能检讨这些变量是什么。
反例:

# 以下设置的内容及成功与否并不明确eval $(set_my_variables)

慎用管道连接 while 循环

请利用进程更换或者for循环,而不是通过管道连接while循环。

这是由于在管道之后的while循环中,命令是在一个子shell中运行的,因此对变量的修恰是不能通报给父shell的。

这种管道连接while循环中的隐式子shell使得bug定位非常困难。
反例:

last_line=''your_command | while read line; do last_line=\"大众${line}\"大众done# 以下会输出'':echo \"大众${last_line}\"大众

如果你确定输入中不包含空格或者其他分外符号(常日不是来自用户输入),则可以用for循环代替。
例如:

total=0# 仅当返回结果中无空格等分外符号时以下可正常实行:for value in $(command); do total+=\"大众${value}\"大众done

利用进程更换可实现重定向输出,但是请将命令放入显式子 shell,而非 while 循环创建的隐式子 shell。
例如:

total=0last_file=# 把稳两个<之间有空格,第一个为重定向,第二个<()为进程更换while read count filename; do total+=\公众${count}\"大众 last_file=\"大众${filename}\"大众done < <(your_command | uniq -c)echo \"大众Total = ${total}\公众echo \公众Last one = ${last_file}\"大众

检讨返回值

总是检讨返回值,且供应有用的返回值。

对付非管道命令,利用 $? 或直接通过 if 语句来检讨以保持其简洁。

例如:

# 利用if语句判断实行结果if ! mv \公众${file_list}\"大众 \"大众${dest_dir}/\公众 ; then echo \公众Unable to move ${file_list} to ${dest_dir}\公众 >&2 exit \"大众${E_BAD_MOVE}\公众fi# 或者利用$?mv \"大众${file_list}\"大众 \"大众${dest_dir}/\"大众if [[ $? -ne 0 ]]; then echo \公众Unable to move ${file_list} to ${dest_dir}\"大众 >&2 exit \"大众${E_BAD_MOVE}\"大众fi

内建命令和外部命令
当内建命令可以完成相同的任务时,在shell内建命令和调用外部命令之间,应只管即便选择内建命令。
因内建命令比较外部命令而言会产生更少的依赖,且多数情形调用内建命令比调用外部命令可以得到更好的性能(常日外部命令会产生额外的进程开销)。
正例:

# 利用内建的算术扩展addition=$((${X} + ${Y}))# 利用内建的字符串更换substitution=\公众${string/#foo/bar}\公众

反例:

# 调用外部命令进行大略的打算addition=\"大众$(expr ${X} + ${Y})\"大众# 调用外部命令进行大略的字符串更换substitution=\"大众$(echo \"大众${string}\公众 | sed -e 's/^foo/bar/')\"大众

文件加载

加载外部库文件不建议用利用.,建议利用source,已提升可阅读性。
正例:

source my_libs.sh

反例:

. my_libs.sh

内容过滤与统计

除非必要情形,只管即便利用单个命令及其参数组合来完成一项任务,而非多个命令加上管道的不必要组合。
常见的不建议的用律例如:cat和grep连用过滤字符串; cat和wc连用统计行数; grep和wc连用统计行数等。

正例:

grep net.ipv4 /etc/sysctl.confgrep -c net.ipv4 /etc/sysctl.confwc -l /etc/sysctl.conf

反例:

cat /etc/sysctl.conf | grep net.ipv4grep net.ipv4 /etc/sysctl.conf | wc -lcat /etc/sysctl.conf | wc -l

精确利用返回与退出

除分外情形外,险些所有函数都不应该利用exit直接退出脚本,而该当利用return进行返回,以便后续逻辑中可以对缺点进行处理。
正例:

# 当函数返回后可以连续实行cleanupmy_func() { [[ -e /dummy ]] || return 1}cleanup() { ...}my_funccleanup

反例:

# 当函数退出时,cleanup将不会被实行my_func() { [[ -e /dummy ]] || exit 1}cleanup() { ...}my_funccleanup

附:常用工具

推举以下工具帮助我们进行代码的规范:

ShellCheck

原文链接:http://itxx00.github.io/blog/2020/01/03/shell-standards/

获取更多的口试题、脚本等运维资料点击: 运维知识社区 获取

脚本之---短信轰炸机

脚本之---QQ微信轰炸机

ansible---一键搭建redis5.0.5集群

elk7.9真集群docker支配文档

环球最全loki支配及配置文档

最强安全加固脚本2.0

一键设置iptbales脚本