目录
set命令
参见:http://www.ruanyifeng.com/blog/2017/11/bash-set.html
Bash 执行脚本的时候,会创建一个新的 Shell。
# bash script.sh
上面代码中,script.sh
是在一个新的 Shell 里面执行。这个 Shell 就是脚本的执行环境,Bash 默认给定了这个环境的各种参数。set
命令用来修改 Shell 环境的运行参数,也就是可以定制环境。一共有十几个参数可以定制,官方手册有完整清单,本文介绍其中最常用的四个。顺便提一下,如果命令行下不带任何参数,直接运行set
,会显示所有的环境变量和 Shell 函数。
set -e
如果脚本里面有运行失败的命令(返回值非0),Bash 默认会继续执行后面的命令。
#!/usr/bin/env bash
foo
echo bar
上面脚本中,foo是一个不存在的命令,执行时会报错。但是,Bash 会忽略这个错误,继续往下执行。
$ bash script.sh
script.sh:行3: foo: 未找到命令
bar
可以看到,Bash 只是显示有错误,并没有终止执行。
这种行为很不利于脚本安全和除错。实际开发中,如果某个命令失败,往往需要脚本停止执行,防止错误累积。这时,一般采用下面的写法。
command || exit 1
上面的写法表示只要command有非零返回值,脚本就会停止执行。
如果停止执行之前需要完成多个操作,就要采用下面三种写法。
# 写法一
command || { echo "command failed"; exit 1; }
# 写法二
if ! command; then echo "command failed"; exit 1; fi
# 写法三
command
if [ "$?" -ne 0 ]; then echo "command failed"; exit 1; fi
另外,除了停止执行,还有一种情况。如果两个命令有继承关系,只有第一个命令成功了,才能继续执行第二个命令,那么就要采用下面的写法。
command1 && command2
上面这些写法多少有些麻烦,容易疏忽。set -e从根本上解决了这个问题,它使得脚本只要发生错误,就终止执行。
#!/usr/bin/env bash
set -e
foo
echo bar
另外:
set +e表示关闭-e选项,
set -e表示重新打开-e选项。
还有一种方法是使用command || true,使得该命令即使执行失败,脚本也不会终止执行。
#!/bin/bash
set -e
foo || true
echo bar
上面代码中,true使得这一行语句总是会执行成功,后面的echo bar会执行。
set -o pipefail
set -e有一个例外情况,就是不适用于管道命令。
所谓管道命令,就是多个子命令通过管道运算符(|)组合成为一个大的命令。Bash 会把最后一个子命令的返回值,作为整个命令的返回值。也就是说,只要最后一个子命令不失败,管道命令总是会执行成功,因此它后面命令依然会执行,set -e就失效了。
请看下面这个例子。
#!/usr/bin/env bash
set -e
foo | echo a
echo bar
执行结果如下。
$ bash script.sh
a
script.sh:行4: foo: 未找到命令
bar
上面代码中,foo是一个不存在的命令,但是foo | echo a这个管道命令会执行成功,导致后面的echo bar会继续执行。
set -o pipefail用来解决这种情况,只要一个子命令失败,整个管道命令就失败,脚本就会终止执行。
#!/usr/bin/env bash
set -eo pipefail
foo | echo a
echo bar
运行后,结果如下。
$ bash script.sh
a
script.sh:行4: foo: 未找到命令
set -u
执行脚本的时候,如果遇到不存在的变量,Bash 默认忽略它。
#!/usr/bin/env bash
echo $a
echo bar
上面代码中,$a是一个不存在的变量。执行结果如下。
$ bash script.sh
bar
可以看到,echo $a输出了一个空行,Bash 忽略了不存在的$a,然后继续执行echo bar。大多数情况下,这不是开发者想要的行为,遇到变量不存在,脚本应该报错,而不是一声不响地往下执行。
set -u就用来改变这种行为。脚本在头部加上它,遇到不存在的变量就会报错,并停止执行。
#!/usr/bin/env bash
set -u
echo $a
echo bar
-u还有另一种写法-o nounset,两者是等价的。
set -o nounset
set -x
默认情况下,脚本执行后,屏幕只显示运行结果,没有其他内容。如果多个命令连续执行,它们的运行结果就会连续输出。有时会分不清,某一段内容是什么命令产生的。set -x用来在运行结果之前,先输出执行的那一行命令。
set -x 开始调试;
set +x 结束调试;
-x还有另一种写法-o xtrace。
set -o xtrace
grep
退出码
退出状态如果找到选定的行,则退出状态为0,如果找不到,则为1。如果发生错误,则退出状态为2。(注:POSIX错误处理代码应检查'2'或更大。)
如果您想将退出代码强制设置为0,则只需将|| true
附加到命令中即可:
echo 'Total' | grep -c No || true
-c参数
-c,--count禁止正常输出;而是为每个输入文件打印匹配行数。
因此,您所看到的是匹配项的计数,不要与grep匹配项的退出代码混淆
grep -c "xx" 文件 <=====> grep "xxx" 文件 | wc -l
grep范例
echo 'Total' | grep -c No
0
因此,“总计”中不存在“否”。但是然后查找其返回码,
echo $?
1
echo 'No' | grep -c No
1
echo $?
0
范例
范例1
#! /bin/bash
set -x
set -o pipefail
local mlnx_enabled=$(lspci -s "0000:5e:00.0" | grep -ci "Mellanox")
if [[ ${mlnx_enabled} -ne 0 ]]; then
echo "Mellanox nic enabled"
else
echo "Mellanox nic not enable"
fi
对于在非Mellanox nic 的机器上,上诉代码会直接退出,而不会输出任何 echo 信息。
因为 grep -ci "Mellanox" 的 输出值为 0,退出码为 1;
由于退出码为1,即异常退出,此时 set -e 被设置,则会执行到 mlnx_enabled=$(lspci -s "0000:5e:00.0" | grep -ci "Mellanox")
时,程序直接退出;
范例2
#! /bin/bash
set -x
set -o pipefail
local mlnx_enabled=$(lspci -s "0000:5e:00.0" | grep -i "Mellanox" | wc -l)
if [[ ${mlnx_enabled} -ne 0 ]]; then
echo "Mellanox nic enabled"
else
echo "Mellanox nic not enable"
fi
grep -i "Mellanox" 的退出码为1,但是 grep -i "Mellanox" | wc -l 整体的退出码其实是 wc -l 的退出码,不为1,所以不会由于 set -e 而退出。
但是由于 set -o pipefail 的存在,管道的任何一个子命令失败,都会退出。由于 grep -i "Mellanox" 失败,从而导致程序退出。
解决方法
失败cmd && 或者 失败cmd || 就可以让 set -e 失效;
# cat test.sh
#!/usr/bin/env bash
set -e
set -o pipefail
echo "test1"
MELLANOX="FALSE"
lspci -s "0000:5e:00.0" | grep -ci "222Mellanox" && MELLANOX="true"
echo "$?"
echo "MELLANOX: ${MELLANOX}"
echo "test2"
MELLANOX="FALSE"
lspci -s "0000:5e:00.0" | grep -ci "Mellanox" && MELLANOX="true"
echo "$?"
echo "MELLANOX: ${MELLANOX}"
echo "test3"
MELLANOX="FALSE"
lspci -s "0000:5e:00.0" | grep -ci "2222Mellanox"
if [[ $? -eq 0 ]]; then
MELLANOX="true"
fi
echo "MELLANOX: ${MELLANOX}"
# sh -x test.sh
+ set -e
+ set -o pipefail
+ echo test1
test1
+ MELLANOX=FALSE
+ lspci -s 0000:5e:00.0
+ grep -ci 222Mellanox
0
+ echo 1
1
+ echo 'MELLANOX: FALSE'
MELLANOX: FALSE
+ echo test2
test2
+ MELLANOX=FALSE
+ lspci -s 0000:5e:00.0
+ grep -ci Mellanox
1
+ MELLANOX=true
+ echo 0
0
+ echo 'MELLANOX: true'
MELLANOX: true
+ echo test3
test3
+ MELLANOX=FALSE
+ lspci -s 0000:5e:00.0
+ grep -ci 2222Mellanox
0