可以使用 shellcheck 来检查 shell 的错误代码的警告和建议.
参照:https://www.jianshu.com/p/161618366866
_
来隔开字母,这是为了和数据库中的命名保持一致。例如:
# 禁止:
local 123user="Tom"
# 推荐:
local user_123="Tom"
# 禁止:
User_Name="Tom"
# 推荐:
user_name="Tom"
# 禁止:
local userName="chenshang"
# 推荐:
local user_name="陈尚"
# 禁止:
local else="nihao"
# 推荐:
local nihao="nihao"
我们往往看到大多数shell脚本的第一行是 #!/bin/bash 这句话,当然也有 #!/bin/sh、#!/usr/bin/bash,这几种写法也都算是正确,当然还有一些野路子的写法,为了避免误导这里就不示例了。本shell规约并不推荐使用上面的任何一种,而是使用 #!/usr/bin/env bash 这种。
#!/usr/bin/env bash
# 主函数 []<-()
function main(){
echo "Hello World!!!"
}
shell脚本的第一行用来指定执行脚本的时候使用的默认解析器是什么, #!/bin/bash这样写就是指定使用 /bin 目录下的 bash来解析。大多数 linux 发行版中默认的shell就是bash,不同的shell下可用的命令不同,比如sh 就比bash 可用的基础命令少很多,这也就是为什么虽然sh是始祖却用的人很少,而它的增强版bash能够后来居上的原因。
shell脚本是逐行解释执行的,在遇到第一行是 #!/bin/bash 的时候就会加载 bash 相关的环境,在遇到 #!/bin/sh 就会加载 sh 相关的环境,避免在执行脚本的时候遇到意想不到的错误。但一开始我并不知道我电脑上安装了哪些shell,默认使用的又是哪一个shell,我脚本移植到别人的计算机上执行,我更不可能知道别人的计算机是Ubuntu还是Arch或是Centos。为了提高程序的移植性,本shell规约规定使用 #!/usr/bin/env bash, #!/usr/bin/env bash 会自己判断使用的shell是什么,并加载相应的环境变量。
我们看一下下面一段脚本,在改变第一行头部的时候,shellcheck给出的建议是什么
举例:
# 禁止:
<-------没有注释
function main(){
local var="Hello World!!!"
echo ${var}
}
# 必须:
# 主函数 []<-()
function main(){
local var="Hello World!!!"
echo ${var}
}
# 禁止:
# error级别的日志 []<-(msg:String) <-------带入参的函数注释
log_error(){
# todo [error]用红色显示
# 将要输出的日志内容
local msg=$1
if [[ x"${msg}" != x"" ]];then
# 注释
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]:[error] $*" >&2
fi
# 建议
# error级别的日志 []<-(msg:String) <-------带入参的函数注释
log_error(){
# todo [error]用红色显示 <------函数内注释
local msg=$1 # 将要输出的日志内容 <------变量的注释紧跟在变量的后面
if [[ x"${msg}" != x"" ]];then
# 注释 <-------函数内注释 `#` 与缩进格式对整齐
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')][$$]:[error] $*" >&2
fi
}
\
进行换行,使用 \
换行的原则是整齐美观
# 禁止
# 脚本使用帮助文档 []<-()
manual(){
cat "$0"|grep -v "less \"\$0\"" |grep -B1 "function " |grep -v "\\--" |sed "s/function //g" |sed "s/(){//g" |sed "s/#//g"
}
# 鼓励
# 脚本使用帮助文档 []<-()
function manual(){
cat "$0"|grep -v "less \"\$0\"" \ <----在函数体里面使用两个空格,来代表缩进
|grep -B1 "function " \ <--- 使用 `\` 将很长的命令拆成多行,增强阅读体验
|grep -v "\\--" \
|sed "s/function //g" \
|sed "s/(){//g" \
|sed "s/#//g"
}
# 禁止:
local user_name = "Tom"
# 必须
local user_name="Tom" <--- 将`=`号前后的空格删除掉
# 不建议:
another_var=$user_name
# 建议:
another_var="${user_name}" <-- 这里需要使用双引号
# 建议:
TURE=0 <-- 定义常量 TRUE
# 建议:
readonly TURE=0 <-- 定义常量 TRUE
# 不建议:
# 主函数:[]<-()
function main(){
name="chenshang" #这里使用local定义一个局部变量
web="${web}" #这里${}内的web是全局变量,之后在函数中在使用web变量都是使用的局部变量
web2="${web}" #对于全局变量,虽然在使用的时候直接使用即可,但还是推荐使用一个局部变量进行接收,然后使用局部变量,以防止在多线程操作的时候出现异常(相当于java中的静态变量在多线程中的时候需要注意线程安全一样,但常量除外)
}
# 建议:
# 主函数:[]<-()
function main(){
local name="chenshang" #这里使用local定义一个局部变量
local web="${web}" #这里${}内的web是全局变量,之后在函数中在使用web变量都是使用的局部变量
local web2="${web}" #对于全局变量,虽然在使用的时候直接使用即可,但还是推荐使用一个局部变量进行接收,然后使用局部变量,以防止在多线程操作的时候出现异常(相当于java中的静态变量在多线程中的时候需要注意线程安全一样,但常量除外)
}
注意: 单引号和双引号的区别
单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的,单引号字串中不能出现单引号(对单引号使用转义符后也不行)。
双引号中的普通字符都会原样输出,单引号中的使用$取值的变量会替换成响应变量的真实值得,然后在进行输出,双引号中可以出现单引号
[]->()
表示。# 建议:
# 函数备注
function main(){
#函数执行的操作
#函数的返回结果
}
# 不建议:
# 函数备注
main(){
#函数执行的操作
#函数的返回结果
}
# 建议:
# 函数备注
main(){
start_date=$1
end_date=$2
#函数执行的操作
hive --hivevar start_date="${start_date}"\
--hivevar end_date="${end_date}"\
-f xx.sql
#函数的返回结果
}
# 不建议:
# 函数备注
main(){
#函数执行的操作
hive --hivevar start_date="${1}"\
--hivevar end_date="${2}"\
-f xx.sql
#函数的返回结果
}
# 函数入参和返回值类型举例子
[]<-() <-- 无入参和返回值
[String]<-(var1:String,var2:String) <-- 两个 string 类型入参,返回值类型了是 String
[Boolean]<-(var1:String,var2:Int) <-- 第一个入参是 string , 第二个入参是 int 类型,返回值类型是 Boolean
[]<-(var1:String) <-- 只有一个 string 类型入参,无返回值
请用隐方(echo+本地变量)的方式来返回“返回值”
# 不建议:
# 检查当前系统版本 [Integer]<-()
function check_version(){
# 函数处理过程
rs=$(数据处理命令)
return "${rs}"
}
# 建议:
# 检查当前系统版本 [Integer]<-()
function check_version(){
# 函数处理过程
rs=$(数据处理命令)
echo "${rs}"
}
## 只有 if 的写法
if [[ condition ]]; then
# statements
fi
## if else 的写法
if [[ condition ]]; then
# statements
else
# statements
fi
## if elif else 的写法
if [[ condition ]]; then
# statements
elif [[ condition ]]; then
# statements
else
# statements
fi
需要注意的两点:
if 后面的判断 使用 双中括号[[]]
if [[ condition ]]; then 写在一行
while
while [[ condition ]]; do
# statements
done
## 读出文件中的每一行
while read -r item ;do
# statements
done < 'file_name'
until [[ condition ]]; do
# statements
done
for (( i = 0; i < 10; i++ )); do
# statements
done
for item in ${array}; do
# statements
done
case word in
pattern )
#statements
;;
*)
#statements
;;
esac
命令
含义
!
保留字,逻辑非
:
不做任何事,只做参数展开
.
读取文件并在shell中执行它
alias
设置命令或命令行别名
bg
将作业置于后台运行
bind
将关键字序列与readline函数或宏捆绑
break
保留字,跳出for、while、until、select循环
builtin
调用命令的内建命令格式,而禁用同名的函数。或者同名的扩展命令
case
保留字,多重选择
cd
切换当前工作目录
command
找出内建和外部命令;寻找内建命令而非同名函数
continue
保留字,到达下次for、while、until、select循环
declare
声明变量定义变量属性
dirs
显示当前存储的列表
disown
将作业从列表中移除
do
保留字,for、while、until、select循环的一部分
done
保留字,for、while、until、select循环的一部分
echo
打印参数
elif
保留字,if结构的一部分
else
保留字,if结构的一部分
enable
开启和关闭内建命令
esac
保留字,case的一部分
eval
将参数作为命令再次处理一遍
exec
以特定程序取代shell或为shell改变I/O
exit
退出shell
export
将变量声明为环境变量
fc
与历史命令一起运行
fg
将作业置于后台运行
fi
保留字,if循环的一部分
for
保留字,for循环的一部分
function
定义一个函数
getops
处理命令行选项
hash
记录并指定命令的路径名
help
显示内建命令的帮助信息
history
显示历史信息
if
保留字,if循环的一部分
in
保留字,case、for、select循环的一部分
jobs
显示后台运行的作业
kill
向进程传送信号
let
使变量执行算术运算
local
定义局部变量
logout
从Shell中注销
popd
从目录栈中弹出目录
pushd
将目录压入栈
pwd
显示当前工作目录
read
从标准输入中读取一行
readonly
将变量定义为只读
return
从函数或脚本返回
select
保留字,生成菜单
set
设置Shell选项
shift
变换命令行参数
suspend
终止Shell的执行
test
评估条件表达式
then
保留字,if结构的一部分
time
保留字,输出统计出来的命令执行时间,其输出格式由TIMEFORMAT变量来控制
times
针对Shell及其子Shell,显示用户和系统CPU的时间和
trap
设置扑捉程序
type
确定命令的源
typeset
声明变量,定义变量属性,与declare等价
ulimit
设置和显示进程占用的资源限制
umask
设置和显示文件权限码
unalias
取消别名定义
unset
取消变量或函数定义
until
保留字,一种循环结构
wait
等待后台作业完成
while
保留字,一种循环结构
如何使用脚本单独调用函数中的某个函数
#!/usr/bin/env bash
# shellcheck disable=SC1091,SC2155
readonly local TRUE=0 && readonly local FALSE=1
# 脚本使用帮助文档
manual(){
cat "$0"|grep -v "less \"\$0\"" \
|grep -B1 "function " \
|grep -v "\\--" \
|sed "s/function //g" \
|sed "s/(){//g" \
|sed "s/#//g" \
|sed 'N;s/\n/ /' \
|column -t \
|awk '{print $1,$3,$2}' \
|column -t
}
######################################################################
# 主函数
main(){
(manual)
}
######################################################################
# 执行函数 [Any]<-(function_name:String,function_parameters:List)
execute(){
function_name=$1
shift # 参数列表以空格为分割左移一位,相当于丢弃掉第一个参数
function_parameters=$*
(${function_name} "${function_parameters}")
}
case $1 in
"-h" | "--help" | "?") (manual);;
"") (main) ;;
*) (execute "$@") ;;
esac