文章目录
Shellcheck是一套用来检查和分析Shell脚本的自动化检查工具。除了检查撰写样式之外,它还能够识别Shell脚本里有什么指令可能存在执行风险,并提供对应的修改方式协助改善Shell脚本的质量。
算术运算
下图来自《跟老男孩学Linux运维:Shell编程实战》
推荐使用$((expression))
进行算术运算,let
和expr
略过。
使用逻辑运算符的时候,返回1
表示true,返回
0表示
false`。
#!/usr/bin/env bash
# bash 中等号两端不能有空格
# let var_name=expression
let sum=1+2
echo ${sum}
# $[expression]
sum=$[ 1 + 2 ]
echo ${sum}
# $((expression))
sum=$(( 1 + 2 ))
echo ${sum}
# $(expr arg1 arg2 arg3 ...)
sum=$(expr 1 + 2 + 4)
echo ${sum}
示例1:
#!/usr/bin/env bash
# 计算 /etc/passwd中第5和第10个用户的 id 号之和
user_id1=$(id -u "$(head -5 /etc/passwd | tail -1 | cut -d: -f1)")
user_id2=$(id -u "$(head -10 /etc/passwd | tail -1 | cut -d: -f1)")
echo "$((user_id1 + user_id2))"
示例2
#!/usr/bin/env bash
# 计算 /etc/profile 和 ~/.bashrc 的空白行数之和
profile_blank_num=$(grep -c "^[[:space:]]*$" /etc/profile);
bashrc_blank_num=$(grep -c "^[[:space:]]*$" ~/.bashrc);
echo "$((profile_blank_num + bashrc_blank_num))"
条件测试
条件测试的两种方式
-
根据命令的状态返回值来判断,返回值为
0
表示真,其他的情况为假#!/usr/bin/env bash # 判断 root 用户是否登录,如果 root 用户已经登录,则执行后面的 echo语句 who | grep "^root\>" > /dev/null && echo "root账号已经登录"
-
使用测试表达式
-
test expression
-
[ expression ]
和上面的test
基本等价 ,注意expression
前后都要和中括号保留一个空格。 -
[[ expression ]]
中可以使用通配符等进行模式匹配,也可以使用&&
、||
、>
、<
等逻辑运算符,这这是不同于[]
的地方,在[]
进行关系运算,一般使用-a
、-o
等代替。 -
数值关系运算,使用
((number_expression))
,返回1
表示true
,返回0
表示false
下图来自《跟老男孩学Linux运维:Shell编程实战》
-
测试表达式的写法
数值测试表达式
用于比较数值的大小。
运算符 | 示例 | 作用 |
---|---|---|
-eq | 2 -eq 3 | 判断两个数值是相等 |
-gt | 2 -gt 3 | 判断 2 是否大于 3 |
-ge | 2 -ge 3 | 判断 2 是否大于等于 3 |
-lt | 2 -lt 3 | 判断 2 是否小于 3 |
-le | 2 -le 3 | 判断 2 是否小于等于 3 |
字符串测试表达式
字符串测试表达式中,如果包含变量比较,需要将变量用双引号引起来,防止因为变量不存在导致的脚本报错。
另外,字符串测试表达式尽可能使用双中括号的形式,如[[ str1 == str2 ]]
运算符 | 示例 | 作用 |
---|---|---|
== | str1 == str2 | 判断 str1 和 str2 是否相等 |
!= | str1 != str2 | 判断 str1 和 str2 是否不相等 |
> | str1 > str2 | 判断 str1 是否大于 str2 |
< | str1 < str2 | 判定 str1 是否小于 str2 |
=~ | str1 =~ “1$” | 判断 str1 是否匹配右侧的 pattern 模式 |
-z | -z str1 | 判断 str1 是否为空字符串 |
-n | -n str1 | 判断 str1 是否为非空字符串 |
文件测试表达式
存在性测试
测试 FILE
表达式是否存在。
-a FILE
-e FILE
存在性及类型测试
运算符 | 示例 | 作用 |
---|---|---|
-b | -b FILE | 判断 FILE 是否存在且为 块设备 |
-c | -c FILE | 判断 FILE 是否存在且为 字符设备 |
-d | -d FILE | 判断 FILE 是否存在且为 目录文件 |
-f | -f FILE | 判断 FILE 是否存在且为 普通文件 |
-h/-l | -h FILE 或者 -l FILE | 判断 FILE是否存在且为符号链接文件 |
-p | -p FILE | 判断 FILE 是否存在且为 管道文件 |
-S | -S FILE | 判断 FILE 是否存在且为 套接字文件 |
文件权限测试表达式
运算符 | 示例 | 作用 |
---|---|---|
-r | -r FILE | 判断 FILE 是否存在且可读 |
-w | -w FILE | 判断 FILE 是否存在且可写 |
-x | -x FILE | 判断 FILE 是否存在且可执行 |
特殊权限测试表达式
运算符 | 示例 | 作用 |
---|---|---|
-g | -g FILE | 判断 FILE 是否存在且拥有 sgid 权限 |
-u | -u FILE | 判断 FILE 是否存在且拥有 suid 权限 |
-k | -k FILE | 判断 FILE 是否存在且拥有 sticky 权限 |
文件非空测试表达式
运算符 | 示例 | 作用 |
---|---|---|
-s | -s FILE | 判断 FILE 是否存在且有内容(非空) |
时间戳测试表达式
运算符 | 示例 | 作用 |
---|---|---|
-N | -N FILE | 判断 FILE 文件自从上一次被读取操作后是否被修改过 |
从属关系测试表达式
运算符 | 示例 | 作用 |
---|---|---|
-O | -O FILE | 判断 FILE 是否存在且当前用户为属主 |
-G | -G FILE | 判断 FILE 是否存在且当前用户为属组 |
双目测试表达式
运算符 | 示例 | 作用 |
---|---|---|
-ef | FILE1 -ef FILE2 | 判断 FILE1 和 FILE2 是否指向同一个文件系统的相同 inode 的硬链接 |
-ot | FIFE1 -ot FILE2 | 判断 FILE1 是否旧于 FILE2 |
-nt | FIFE1 -nt FILE2 | 判断 FILE1 是否新于 FILE2 |
组合测试条件
逻辑条件 | 方式一 | 方式二 |
---|---|---|
逻辑与 | 命令:COMMAND1 && COMMAND2 测试表达式 [ expression1 ] && [ expression2 ] | 命令:不支持 测试表达式 [ expression1 -a expression2 ] |
逻辑或 | 命令:COMMAND1 || COMMAND2 测试表达式 [ expression1 ] || [ expression2 ] | 命令:不支持 测试表达式 [ expression1 -o expression2 ] |
逻辑非 | 命令:! COMMAND 测试表达式 ! [ expression ] 或者[ ! expresstion ] 注意 ! 后要有一个空格 | 命令:! COMMAND 测试表达式! [ expression ] 或者[ ! expresstion ] 注意 ! 后要有一个空格 |
方式二之所以是不支持命名模式,是因为命令是不能放在中括号中执行的,了解即可。推荐方式一的写法。
参数传递
传递给脚本的参数依次放置在$1
、$2
、$3
、$4
…的变量中,如果参数的个数大于9
,则使用${n}
。
#!/usr/bin/env bash
# 接收两个数字,计算他们的和,并输出
echo $(( $1+$2 ))
bash demo.sh 3 5
打印出 8
参数轮替,又叫位置参数轮替。shift [n]
,n
表示每次操作被剔除去的参数个数,有点像先进先出,通过shift
不断将先进的参数弹出。
#!/usr/bin/env bash
# 有参数 tom jack askou joy
# 参数轮替
echo "$1" # tom
shift 2
echo "$1" # askou
shift 1
echo "$1" # joy
执行脚本
bash demo.sh /etc/profile ~/.bashrc
# 文件 /etc/profile 的空白行数为 13
# 文件 /home/xingmu/.bashrc 的空白行数为 34
# 两个文件的空白行之和为 47
特殊变量
变量 | 作用 |
---|---|
$0 | 指向脚本执行时候的名称/脚本路径 |
$# | 脚本收到的参数个数 |
$? | 命令/脚本的执行状态 |
$* | 比如传递了 5 个参数,那么对于"$*" 来说,这 5 个参数会合并到一起形成一份数据,它们之间是无法分割的; |
$@ | 对于"$@" 来说,这 5 个参数是相互独立的,它们是 5 份数据。可以使用循环依次输出 |
如果使用 echo 直接输出"$*"
和"$@"
做对比,是看不出区别的;但如果使用 for 循环来逐个输出数据,立即就能看出区别来。
脚本状态返回值默认是脚本最后一条命令的返回值,也可以自定义状态值,使用
exit [n]
返回,n
就是自定义的状态码,自定义状态码也要遵守正常返回状态码0
,其他情况下返回1-255
的任意数值。需要明白的是,
shell
遇到exit
指令就会结束执行,同时返回状态码。
if 条件判断
示例1
#!/usr/bin/env bash
# 变量 hostName 是否为空、localhost或者localhost.localdomain,如果是则输出 expression.test.com,否则输出 host
hostName="localhost.localdomain"
if echo $hostName | grep -qE "^[[:space:]]$|localhost(\.localdomain)?$"; then
echo "change hostname to expression.test.com"
else
echo "hostname is $hostName"
fi
示例2: 判断给定用户是否存在,如果存在给出提示,如果不存在,则添加用户
#!/usr/bin/env bash
# 判断给定用户是否存在,如果存在给出提示,如果不存在,则添加用户
if [ $# -lt 1 ]; then
echo "请输入用户名"
elif ! echo "$1" | grep -qE "^[[:alnum:]]+$"; then
echo "无效用户名"
else
if grep -Eq "^$1\>" /etc/passwd; then
echo "用户 $1 已存在"
else
sudo useradd "$1"
echo "用户 $1 已经添加成功"
fi
fi
示例3 : 通过命令传递两个文件的路径,计算其空白行数之和
#!/usr/bin/env bash
# 通过命令传递两个文件的路径,计算其空白行数之和
if [ ! -f "$1" ] || [ ! -f "$2" ]; then
echo "参数必须为已经存在的普通文件"
exit 1
fi
first_second_blank_line=$(grep -Ec "^[[:space:]]*$" "$1")
echo "文件 $1 的空白行数为 $first_second_blank_line"
second_blank_line=$(grep -Ec "^[[:space:]]*$" "$2")
echo "文件 $2 的空白行数为 $second_blank_line"
echo "两个文件的空白行之和为 $(( first_second_blank_line + second_blank_line))"
示例4 :判断给定用户名的 id 号是奇数还是偶数
#!/usr/bin/env bash
# 判断给定用户的 id 号是奇数还是偶数
if ! grep -Eq "^$1\>" /etc/passwd; then
echo "用户 $1 不存在"
exit 1
fi
user_id=$(id -u $1)
echo "用户 $1 的id为 $user_id"
if [ $((user_id % 2)) -eq 0 ]; then
echo "用户 $1 的id是偶数"
else
echo "用户 $1 的id是奇数"
fi
示例5 : 使用算术的逻辑运算重写示例4,表达式就变得简单了
#!/usr/bin/env bash
# 判断给定用户的 id 号是奇数还是偶数
if ! grep -Eq "^$1\>" /etc/passwd; then
echo "用户 $1 不存在"
exit 1
fi
user_id=$(id -u $1)
echo "用户 $1 的id为 $user_id"
if ((user_id % 2==0)); then
echo "用户 $1 的id是偶数"
else
echo "用户 $1 的id是奇数"
fi
a-z ↩︎