参考文章
http://www.ruanyifeng.com/blog/2017/11/bash-set.html
[TOC]
shell语法检查与执行
verbose模式
读取脚本时,显示读到的每一行
1 | $ bash -v script.sh |
语法检查调试模式
shell读取脚本时检查语法,一旦发现语法错误即在终端输出,无语法错误不输出
1 | $ bash -n script.sh |
分步执行
1 | $ bash -x script.sh |
shell 中 && || () {} 用法
&& 运算符:
格式
1 | command1 && command2 |
&&左边的命令(命令1)返回真(即返回0,成功被执行)后,&&右边的命令(命令2)才能够被执行;换句话说,“如果这个命令执行成功&&那么执行这个命令”。
语法格式如下:
1 | command1 && command2 && command3 ... |
- 命令之间使用 && 连接,实现逻辑与的功能。
- 只有在 && 左边的命令返回真(命令返回值 $? == 0),&& 右边的命令才会被执行。
- 只要有一个命令返回假(命令返回值 $? == 1),后面的命令就不会被执行。
|| 运算符:
格式
1 | command1 || command2 |
||则与&&相反。如果||左边的命令(command1)未执行成功,那么就执行||右边的命令(command2);或者换句话说,“如果这个命令执行失败了||那么就执行这个命令。
- 命令之间使用 || 连接,实现逻辑或的功能。
- 只有在 || 左边的命令返回假(命令返回值 $? == 1),|| 右边的命令才会被执行。这和 c 语言中的逻辑或语法功能相同,即实现短路逻辑或操作。
- 只要有一个命令返回真(命令返回值 $? == 0),后面的命令就不会被执行。
||与&&
1 | command1 && command2 || command3 |
将||与&&组合使用则表达,如果command1执行成功,则执行command2,否则执行command3
下面是一个shell脚本中常用的||组合示例
1 | echo $BASH |grep -q 'bash' || { exec bash "$0" "$@" || exit 1; } 系统调用exec是以新的进程去代替原来的进程,但进程的PID保持不变。因此,可以这样认为,exec系统调用并没有创建新的进程,只是替换了原来进程上下文的内容。原进程的代码段,数据段,堆栈段被新的进程所代替。 |
() 运算符:
如果希望把几个命令合在一起执行,shell提供了两种方法。既可以在当前shell也可以在子shell中执行一组命令。
格式:
1 | (command1;command2;command3....) 多个命令之间用;分隔 |
- 一条命令需要独占一个物理行,如果需要将多条命令放在同一行,命令之间使用命令分隔符(;)分隔。执行的效果等同于多个独立的命令单独执行的效果。
- () 表示在当前 shell 中将多个命令作为一个整体执行。需要注意的是,使用 () 括起来的命令在执行前面都不会切换当前工作目录,也就是说命令组合都是在当前工作目录下被执行的,尽管命令中有切换目录的命令。
- 命令组合常和命令执行控制结合起来使用。
{} 运算符:
如果使用{}来代替(),那么相应的命令将在子shell而不是当前shell中作为一个整体被执行,只有在{}中所有命令的输出作为一个整体被重定向时,其中的命令才被放到子shell中执行,否则在当前shell执行。
它的一般形式为:
1 | { command1;command2;command3… } 注意:在使用{}时,{}与命令之间必须使用一个空格 |
shell脚本获取当前目录和文件夹名
1 | #!/bin/bash |
可以用${ }分别替换得到不同的值:file=/dir1/dir2/dir3/my.file.txt
${file#/}:删掉第一个 / 及其左边的字符串:dir1/dir2/dir3/my.file.txt
${file##/}:删掉最后一个 / 及其左边的字符串:my.file.txt
${file#.}:删掉第一个 . 及其左边的字符串:file.txt
${file##.}:删掉最后一个 . 及其左边的字符串:txt
${file%/}:删掉最后一个 / 及其右边的字符串:/dir1/dir2/dir3
${file%%/}:删掉第一个 / 及其右边的字符串:(空值)
${file%.}:删掉最后一个 . 及其右边的字符串:/dir1/dir2/dir3/my.file
${file%%.}:删掉第一个 . 及其右边的字符串:/dir1/dir2/dir3/my
Shell脚本输出颜色
介绍
1 | echo -e "\033[43;35m david use echo say Hello World \033[0m \n" |
1 | echo -e "\033[47;30;5m david use echo say \033[0m Hello World \n" |
1 | echo -e "\033[20;1H\033[1;4;32m david use echo say \033[0m Hello World \n" |
这行命令首先\033[20;1H将光标移动到终端第20行第1列,之后的\033[1;4;32m将文本属性设置为高亮、带下划线且颜色为绿色,然后输出Hello,world;最后\033[0m将终端属性设为缺省,这样就不会看到连命令完成后的命令提示符也变了样儿了。我们可以通过各种命令的组合可以实现对终端输出地复杂控制。
1 | 0 重新设置属性到缺省设置 |
脚本颜色变量
1 | RED_COLOR='\E[1;31m' |
set命令
set -u
遇到不存在的变量,Bash报错并停止执行。还有另外一中写法
-u
还有另一种写法-o nounset
,两者是等价的。set -x
默认情况下,脚本执行后,屏幕只显示运行结果,没有其他内容。如果多个命令连续执行,它们的运行结果就会连续输出。有时会分不清,某一段内容是什么命令产生的。
set -x
用来在运行结果之前,先输出执行的那一行命令。-x
还有另一种写法-o xtrace
。set -e
它使得脚本只要发生错误,就终止执行。
set -e
根据返回值来判断,一个命令是否运行失败。但是,某些命令的非零返回值可能不表示失败,或者开发者希望在命令失败的情况下,脚本继续执行下去。这时可以暂时关闭set -e
,该命令执行结束后,再重新打开set -e
set +e
表示关闭-e
选项,set -e
表示重新打开-e
选项。-e
还有另一种写法-o errexit
set -o pipefail
set -e
有一个例外情况,就是不适用于管道命令。所谓管道命令,就是多个子命令通过管道运算符(|
)组合成为一个大的命令。Bash 会把最后一个子命令的返回值,作为整个命令的返回值。也就是说,只要最后一个子命令不失败,管道命令总是会执行成功,因此它后面命令依然会执行,set -e
就失效了。
总结
set
命令的上面这四个参数,一般都放在一起使用
1 | # 写法一 |
这两种写法建议放在所有 Bash 脚本的头部。
另一种办法是在执行 Bash 脚本的时候,从命令行传入这些参数。
1 | $ bash -euxo pipefail script.sh |
Bash的错误处理
如果脚本里面有运行失败的命令(返回值非0),Bash 默认会继续执行后面的命令。
1
2
3
4
5 > #!/usr/bin/env bash
>
> foo
> echo bar
>
上面脚本中,foo
是一个不存在的命令,执行时会报错。但是,Bash 会忽略这个错误,继续往下执行。
1
2
3
4 > $ bash script.sh
> script.sh:行3: foo: 未找到命令
> bar
>
可以看到,Bash 只是显示有错误,并没有终止执行。
这种行为很不利于脚本安全和除错。实际开发中,如果某个命令失败,往往需要脚本停止执行,防止错误累积。这时,一般采用下面的写法。
1
2 > command || exit 1
>
上面的写法表示只要command
有非零返回值,脚本就会停止执行。
如果停止执行之前需要完成多个操作,就要采用下面三种写法。
1
2
3
4
5
6
7
8
9
10 > # 写法一
> 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
>
另外,除了停止执行,还有一种情况。如果两个命令有继承关系,只有第一个命令成功了,才能继续执行第二个命令,那么就要采用下面的写法。
1
2 > command1 && command2
>
command || true
,使得该命令即使执行失败,脚本也不会终止执行。
1
2
3
4
5
6 > #!/bin/bash
> set -e
>
> foo || true
> echo bar
>
上面代码中,true
使得这一行语句总是会执行成功,后面的echo bar
会执行。
常用表达式
文件表达式
- -e filename 如果 filename存在,则为真
- -d filename 如果 filename为目录,则为真
- -f filename 如果 filename为常规文件,则为真
- -L filename 如果 filename为符号链接,则为真
- -r filename 如果 filename可读,则为真
- -w filename 如果 filename可写,则为真
- -x filename 如果 filename可执行,则为真
- -s filename 如果文件长度不为0,则为真
- -h filename 如果文件是软链接,则为真
- filename1 -nt filename2 如果 filename1比 filename2新,则为真。
- filename1 -ot filename2 如果 filename1比 filename2旧,则为真。
整数变量表达式
- -eq 等于
- -ne 不等于
- -gt 大于
- -ge 大于等于
- -lt 小于
- -le 小于等于
字符串变量表达式
- If [ $a = $b ] 如果string1等于string2,则为真,字符串允许使用赋值号做等号
- if [ $string1 != $string2 ] 如果string1不等于string2,则为真
- if [ -n $string ] 如果string 非空(非0),返回0(true)
- if [ -z $string ] 如果string 为空,则为真
- if [ $sting ] 如果string 非空,返回0 (和-n类似)
- if [ ! -d $num ]
- if [ 表达式1 –a 表达式2 ]
- if [ 表达式1 –o 表达式2 ]
Shell脚本中调用另外一个脚本的方法
在Linux平台上开发,经常会在console(控制台)上执行另外一个脚本文件,经常用的方法有:./my.sh 或 source my.sh 或 . my.sh;这三种方法有什么不同呢?我们先来了解一下在一个shell脚本中如何调用另外一个shell脚本,其方法有 fork exec source。
fork ( /directory/script.sh) :
如果shell中包含执行命令,那么子命令并不影响父级的命令,在子命令执行完后再执行父级命令。子级的环境变量不会影响到父级。
fork是最普通的, 就是直接在脚本里面用/directory/script.sh来调用script.sh这个脚本. 运行的时候开一个sub-shell执行调用的脚本,sub-shell执行的时候,parent-shell还在。
sub-shell执行完毕后返回parent-shell. sub-shell从parent-shell继承环境变量.但是sub-shell中的环境变量不会带回parent-shell
exec (exec /directory/script.sh):
执行子级的命令后,不再执行父级命令。
exec与fork不同,不需要新开一个sub-shell来执行被调用的脚本. 被调用的脚本与父脚本在同一个shell内执行。但是使用exec调用一个新脚本以后, 父脚本中exec行之后的内容就不会再执行了。这是exec和source的区别
source (source /directory/script.sh):
执行子级命令后继续执行父级命令,同时子级设置的环境变量会影响到父级的环境变量。
与fork的区别是不新开一个sub-shell来执行被调用的脚本,而是在同一个shell中执行. 所以被调用的脚本中声明的变量和环境变量, 都可以在主脚本中得到和使用.
以上三种就是调用shell脚本的不同方法,./my.sh即是fork的方法,source my.sh和. my.sh(点加空格加脚本文件)既是source的方法。
在linux系统上,搭建嵌入式开发平台,在交叉编译代码之前,都需要执行脚本设置环境变量,切记需要使用sourc 或 点的方式执行shell脚本,原因如上。
Shell脚本执行mysql命令
mysql -hhostname -uuser -ppasswd -e “mysql_cmd”
mysql -hhostname -uuser -ppasswd << EOF
mysql_cmd
EOF
Shell for循环多个变量
需求:需要输出以下2开头的端口号和其对应的文件
1 | port and port_k8s_xxx.conf |
1 | #! /bash/shell |
Shell中去除字符串前后空格的方法
1 | [root@localhost ~]# echo ' A B C ' | awk '{gsub(/^\s+|\s+$/, "");print}' |
Shell脚本操作mysql数据库
mysql -hhostname -Pport -uusername -ppassword -e 相关mysql的sql语句,不用在mysql的提示符下运行mysql,即可以在shell中操作mysql的方法。
1 | #!/bin/bash |
1 | #创建数据库 |
1 | mysql -uroot -h$host -p$password << EOF |