推举(Preferable):用户理应采取,但如有分外情形,可以不采取;
必须(Mandatory):用户必须采取(除非是少数非常分外的情形,才能不采取);
注:未明确指明的则默认为必须(Mandatory)
紧张参考如下文档:
Google Shell Style Guide
Bash Hackers Wiki
源文件根本利用场景.sh
作为扩展名,且应是不可实行的。.sh
作为特定措辞后缀的扩展名,可以和其他措辞编写的库文件加以区分。_
或连字符-
, 建议可实行文件利用连字符,库文件利用下划线。正例:
my-useful-bin
my_useful_libraries.sh
myusefullibraries.sh
反例:
My_Useful_Bin
myUsefulLibraries.sh
LF
。
示例:
# 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.\公众
空缺字符
除了在行结束利用换行符,空格是源文件中唯一许可涌现的空缺字符。
字符串中的非空格空缺字符,利用转义字符
不许可行前利用tab缩进,如果利用tab缩进,必须设置1个tab为4个空格
不应在行尾涌现没故意义的空缺字符
垃圾清理(推举)对从来没有用到的或者被注释的方法、变量等要武断从代码中清理出去,避免过多垃圾造成滋扰。
构造利用bashBash 是唯一被许可利用的可实行脚本shell。
可实行文件必须以 #!/bin/bash
开始。请利用set
来设置shell的选项,使得用bash <script_name>
调用你的脚本时不会毁坏其功能。
限定所有的可实行shell脚本为bash使得我们安装在所有打算机中的shell措辞保持同等性。正例:
#!/bin/bash
set -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
管道
|
以及逻辑运算 ||
和 &&
。正例:# 单行管道连接,管道旁边空格
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
fi
done
反例:
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
}
通过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}\"大众 ;;
esac
done
将文件中所有的函数统一放在常量下面。不要在函数之间隐蔽可实行代码。
如果你有函数,请将他们统一放在文件头部。只有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(mrmonkey): Handle the unlikely edge cases (bug ####)
# TODO(--bug=123456): remove the \"大众Last visitors\公众 feature
::
分隔包名。函数名之后必须有圆括号。::
来分隔包名。函数名和圆括号之间没有空格,大括号必须和函数名位于同一行。当函数名后存在 ()
时,关键词 function 是多余的,建议不带 function 的写法,但至少做到同一项目内风格保持同等。正例:
# Single function
my_func() {
...
}
# Part of a package
mypackage::my_func() {
...
}
反例:
function my_func
{
...
}
规则同函数名同等。
循环中的变量名该当和正在被循环的变量名保持相似的名称。示例:
for zone in ${zones}; do
something_with \公众${zone}\"大众
done
全部大写,用下划线分隔,声明在文件的顶部。
常量和任何导出到环境中的变量都该当大写。示例:
# Constant
readonly PATH_TO_FILES='/some/path'
# Both constant and environment
declare -xr BACKUP_SID='PROD'
有些情形下首次初始化及常量(例如,通过getopts),因此,在getopts中或基于条件来设定常量是可以的,但之后该当立即设置其为只读。值得把稳的是,在函数中利用 declare 对全局变量无效,以是推举利用 readonly 和 export 来代替。示例:
VERBOSE='false'
while getopts 'v' flag; do
case \"大众${flag}\"大众 in
v) VERBOSE='true' ;;
esac
done
readonly VERBOSE
利用 readonly 或者 declare -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
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 c
echo \"大众${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 c
echo \"大众$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.sh
func_t() {
echo num: $#
echo args: 1:$1 2:$2 3:$3
}
func_t \公众$@\"大众
func_t \"大众$\公众
# 当实行 ./t.sh a b c 时输出如下:
num: 3
args: 1:a 2:b 3:c
num: 1
args: 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 arguments
if [ \"大众filename\"大众 == f ]; then
echo \"大众Match\"大众
fi
尽可能利用变量引用,而非字符串过滤。
Bash可以很好的处理空字符串测试,请利用空/非空字符串测试方法,而不是过滤字符,让代码具有更高的可读性。正例:
if [[ \"大众${my_var}\公众 = \公众some_string\"大众 ]]; then
do_something
fi
反例:
if [[ \公众${my_var}X\"大众 = \"大众some_stringX\"大众 ]]; then
do_something
fi
正例:
# 利用-z测试字符串为空
if [[ -z \"大众${my_var}\公众 ]]; then
do_something
fi
反例:
# 利用空引号测试空字符串,能用但不推举
if [[ \"大众${my_var}\公众 = \公众\公众 ]]; then
do_something
fi
正例:
# 利用-n测试非空字符串
if [[ -n \公众${my_var}\"大众 ]]; then
do_something
fi
反例:
# 测试字符串非空,能用但不推举
if [[ \"大众${my_var}\"大众 ]]; then
do_something
fi
文件名扩展
当进行文件名的通配符扩展时,请指定明确的路径。
当目录中有分外文件名如以 -
开头的文件时,利用带路径的扩展通配符 ./
比不带路径的 要安全很多。
# 例如目录下有以下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 directory
removed `./somefile'
该当避免利用eval。
Eval在用于分配变量时会修正输入内容,但设置变量的同时并不能检讨这些变量是什么。反例:
# 以下设置的内容及成功与否并不明确
eval $(set_my_variables)
请利用进程更换或者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=0
last_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
# 利用内建的算术扩展
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.conf
grep -c net.ipv4 /etc/sysctl.conf
wc -l /etc/sysctl.conf
反例:
cat /etc/sysctl.conf | grep net.ipv4
grep net.ipv4 /etc/sysctl.conf | wc -l
cat /etc/sysctl.conf | wc -l
除分外情形外,险些所有函数都不应该利用exit直接退出脚本,而该当利用return进行返回,以便后续逻辑中可以对缺点进行处理。正例:
# 当函数返回后可以连续实行cleanup
my_func() {
[[ -e /dummy ]] || return 1
}
cleanup() {
...
}
my_func
cleanup
反例:
# 当函数退出时,cleanup将不会被实行
my_func() {
[[ -e /dummy ]] || exit 1
}
cleanup() {
...
}
my_func
cleanup
推举以下工具帮助我们进行代码的规范:
ShellCheck
原文链接:http://itxx00.github.io/blog/2020/01/03/shell-standards/
脚本之---短信轰炸机
脚本之---QQ微信轰炸机
ansible---一键搭建redis5.0.5集群
elk7.9真集群docker支配文档
环球最全loki支配及配置文档
最强安全加固脚本2.0
一键设置iptbales脚本