#!SHEBANG
CONFIGURATION_VARIABLES
FUNCTION_DEFINITIONS
MAIN_CODE
文件中标明解释器的命令叫
shebang
,该字串以#!
开头,并放于文件的第一行开头,操作系统的加载程序在执行时可以使用这一行来加载此文件的解释器,使其成为一个自可执行的脚本。
Bash
作为唯一的shell脚本shebang
!正例:
#!/bin/bash
echo
反例:
#!/bin/sh
echo
function
函数前后用空行隔开vim
中相关设置:set autoindent
set smartindent "indent when
set tabstop=4 "tab width
set softtabstop=4 "backspace
set shiftwidth=4 "indent width
set expandtab "expand tab to space
set shiftround
here document
或者嵌入的换行符等合适的方法使其变短。示例:
# 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."
_
或连字符 -
, 建议统一使用下划线。test
。function
是多余的,但是建议保留 function 的写法,使函数整洁明了。 正例:# Single function
function my_func()
{
...
}
PATH
、HOME
、IFS
等。MY_
。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
a=1 b=2;
正例:
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
...
}
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
grep
中使用|
时需要注意此处|
左右不可随意添加空格。如,搜索aaa2bccc
中2
和b
正例:
echo "aaa2bccc" | grep -Eo "2|b"
反例:
echo "aaa2bccc" | grep -Eo "2 | b"
( list )
在圆括号之间放置一列命令将创建一个子shell环境,列表中的每个命令将在该子shell中执行。由于该列表是在子shell中执行的,所以在子shell完成后,变量分配将不再有效。{ list; }
在花括号之间放置一个命令列表将导致该列表在当前shell上下文中执行。不创建子shell。必须在列表后面使用分号(或换行符)。当花括号与list
在同一行时,必须使用分号和空格隔开。; do
, ; then
和 while
, for
, if
,elif
放在同一行。另 else
应该单独一行。正例:
function clean_up()
{
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
}
;&
和 ;;&
符号。示例:
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
source
, set
声明和常量设置在函数声明之前完成。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
, 在随后的圆括号里写上你的名字,邮件地址, 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 err() {
echo "[$(date +'%FT%T%z')]: $@" >&2
}
if ! do_something; then
err "Unable to do_something"
exit "${E_DID_NOTHING}"
fi
var" ,但具体也要视情况而定。
正例:
# 位置变量和特殊变量,可以不用大括号:
echo "Positional: $1" "$5" "$3"
# $! 最后运行的后台Process的PID
# $- 使用Set命令设定的Flag一览
# $_ 在前台执行的前一个命令的最后一个参数
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"
[[
中模式匹配的引号规则*
# '单引号' 表示禁用变量替换
# "双引号" 表示需要变量替换
# 示例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/null "$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:
# 示例11:如果解析变量作为一个列表,则不能使用引号
list="one two three"
# you MUST NOT quote $list here
for word in $list; do
...
done
正例:
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
正例:
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 $(set_my_variables)
last_line='NULL'
your_command | while read line; do
last_line="${line}"
done
# 以下会输出'NULL':
echo "${last_line}"
例如:
total=0
# 仅当返回结果中无空格等特殊符号时以下可正常执行:
for value in $(command); do
total+="${value}"
done
例如:
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
正例:
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
正例:
# 当函数返回后可以继续执行cleanup
my_func() {
[[ -e /dummy ]] || return 1
}
cleanup() {
...
}
my_func
cleanup
反例:
# 当函数退出时,cleanup将不会被执行
my_func() {
[[ -e /dummy ]] || exit 1
}
cleanup() {
...
}
my_func
cleanup
本文分享自 WriteSimpleDemo 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!