type
status
date
slug
summary
tags
category
icon
password
Property
set、shopt
Bash 执行脚本时,会创建一个子 Shell:
上面代码中,
script.sh
是在一个子Shell 里面执行。这个子 Shell 就是脚本的执行环境,Bash 默认给定了这个环境的各种参数。set
命令用来修改子 Shell 环境的运行参数,即定制环境,一共有十几个参数可以定制。如果命令行下不带任何参数,直接运行
set
,会显示所有的环境变量和 Shell 函数。set -u
执行脚本时,如果遇到不存在的变量,Bash 默认忽略它:
$a
是一个不存在的变量,执行结果如下:echo $a
输出了一个空行,Bash 忽略了不存在的$a
,然后继续执行echo bar
。大多数情况下,这不是开发者想要的行为,遇到变量不存在,脚本应该报错,而不是一声不响地往下执行。set -u
就用来改变这种行为。脚本在头部加上它,遇到不存在的变量就会报错,并停止执行。运行结果如下:
可以看到,脚本报错了,并且不再执行后面的语句。
-u
还有另一种写法o nounset
,两者是等价的:set -x
默认情况下,脚本执行后,只输出运行结果,没有其他内容。如果多个命令连续执行,它们的运行结果就会连续输出。有时会分不清,某一段内容是什么命令产生的。
set -x
用来在运行结果之前,先输出执行的那一行命令。执行上面的脚本,结果如下:
执行
echo bar
之前,该命令会先打印出来,行首以+
表示。这对于调试复杂的脚本是很有用的。-x
还有另一种写法o xtrace
。脚本当中如果要关闭命令输出,可以使用
set +x
。上面的例子中,只对特定的代码段打开命令输出。
Bash 的错误处理
如果脚本里面有运行失败的命令(返回值非
0
),Bash 默认会继续执行后面的命令。上面脚本中,
foo
是一个不存在的命令,执行时会报错。但是,Bash 会忽略这个错误,继续往下执行。这种行为很不利于脚本安全和除错。实际开发中,如果某个命令失败,往往需要脚本停止执行,防止错误累积。这时,一般采用下面的写法。
如果停止执行之前需要完成多个操作,就要采用下面三种写法。
另外,除了停止执行,还有一种情况。如果两个命令有继承关系,只有第一个命令成功了,才能继续执行第二个命令,那么就要采用下面的写法。
set -e
上面这些写法多少有些麻烦,容易疏忽。
set -e
从根本上解决了这个问题,它使得脚本只要发生错误,就终止执行。执行结果如下:
set -e
根据返回值来判断,一个命令是否运行失败。但是,某些命令的非零返回值可能不表示失败,或者开发者希望在命令失败的情况下,脚本继续执行下去。这时可以暂时关闭set -e
,该命令执行结束后,再重新打开set -e
。set +e
表示关闭-e
选项,set -e
表示重新打开-e
选项还有一种方法是使用
command || true
,使得该命令即使执行失败,脚本也不会终止执行。上面代码中,
true
使得这一行语句总是会执行成功,后面的echo bar
会执行。-e
还有另一种写法o errexit
。set -o pipefail
set -e
有一个例外情况,就是不适用于管道命令。所谓管道命令,就是多个子命令通过管道运算符(
|
)组合成为一个大的命令。Bash 会把最后一个子命令的返回值,作为整个命令的返回值。也就是说,只要最后一个子命令不失败,管道命令总是会执行成功,因此它后面命令依然会执行,set -e
就失效了。执行结果如下:
foo
是一个不存在的命令,但是foo | echo a
这个管道命令会执行成功,导致后面的echo bar
会继续执行。set -o pipefail
用来解决这种情况,只要一个子命令失败,整个管道命令就失败,脚本就会终止执行。运行后,结果如下:
set -E
一旦设置了
-e
参数,会导致函数内的错误不会被trap
命令捕获。-E
参数可以纠正这个行为,使得函数也能继承trap
命令。上面示例中,
myfunc
函数内部调用了一个不存在的命令foo
,导致执行这个函数会报错。但是,由于设置了
set -e
,函数内部的报错并没有被trap
命令捕获,需要加上-E
参数才可以。执行上面这个脚本,就可以看到
trap
命令生效了。其他参数
set
命令还有一些其他参数。set -n
:等同于set -o noexec
,不运行命令,只检查语法是否正确。
set -f
:等同于set -o noglob
,表示不对通配符进行文件名扩展。
set -v
:等同于set -o verbose
,表示打印 Shell 接收到的每一行输入。
set -o noclobber
:防止使用重定向运算符>
覆盖已经存在的文件。
上面的
-f
和-v
参数,可以分别使用set +f
、set +v
关闭。set 命令总结
重点介绍的
set
命令的几个参数,一般都放在一起使用。这两种写法建议放在所有 Bash 脚本的头部。
另一种办法是在执行 Bash 脚本的时候,从命令行传入这些参数。
shopt 命令
shopt
命令用来调整 Shell 的参数,跟set
命令的作用很类似。之所以会有这两个类似命令的主要原因是,set
是从 Ksh 继承的,属于 POSIX 规范的一部分,而shopt
是 Bash 特有的。直接输入
shopt
可以查看所有参数,以及它们各自打开和关闭的状态。shopt
命令后面跟着参数名,可以查询该参数是否打开。-s
用来打开某个参数
-u
用来关闭某个参数
举例来说,
histappend
这个参数表示退出当前 Shell 时,将操作历史追加到历史文件中。这个参数默认是打开的,如果使用下面的命令将其关闭,那么当前 Shell 的操作历史将替换掉整个历史文件。-q
的作用也是查询某个参数是否打开,但不是直接输出查询结果,而是通过命令的执行状态($?
)表示查询结果。如果状态为0
,表示该参数打开;如果为1
,表示该参数关闭。
上面命令查询
globstar
参数是否打开。返回状态为1
,表示该参数是关闭的。这个用法主要用于脚本,供if
条件结构使用。脚本除错
编写 Shell 脚本的时候,一定要考虑到命令失败的情况,否则很容易出错。
如果目录
$dir_name
不存在,cd $dir_name
命令就会执行失败。这时,就不会改变当前目录,脚本会继续执行下去,导致rm *
命令删光当前目录的文件。如果改成下面的样子,也会有问题。
只有
cd $dir_name
执行成功,才会执行rm *
。但是,如果变量$dir_name
为空,cd
就会进入用户主目录,从而删光用户主目录的文件。下面的写法才是正确的。
如果不放心删除什么文件,可以先打印出来看一下。
bash
的-x
参数
bash
的-x
参数可以在执行每一行命令之前,打印该命令。一旦出错,这样就比较容易追查。加上
-x
参数,执行每条命令之前,都会显示该命令。行首为
+
的行,显示该行是所要执行的命令,下一行才是该命令的执行结果。下面再看一个
-x
写在脚本内部的例子。上面的脚本执行之后,会输出每一行命令。
输出的命令之前的
+
号,是由系统变量PS4
决定,可以修改这个变量。另外,
set
命令也可以设置 Shell 的行为参数,有利于脚本除错。环境变量
有一些环境变量常用于除错。
LINENO
变量
LINENO
返回它在脚本里面的行号。执行上面的脚本
test.sh
,$LINENO
会返回3
。FUNCNAME
变量
FUNCNAME
返回一个数组,内容是当前的函数调用堆栈。该数组的0号成员是当前调用的函数,1号成员是调用当前函数的函数,以此类推。执行上面的脚本
test.sh
,结果如下。执行
func1
时,变量FUNCNAME
的0号成员是func1
,1号成员是调用func1
的主脚本main
。执行func2
时,变量FUNCNAME
的0号成员是func2
,1号成员是调用func2
的func1
。BASH_SOURCE
变量
BASH_SOURCE
返回一个数组,内容是当前的脚本调用堆栈。该数组的0号成员是当前执行的脚本,1号成员是调用当前脚本的脚本,以此类推,跟变量FUNCNAME
是一一对应关系。下面有两个子脚本
lib1.sh
和lib2.sh
。然后,主脚本
main.sh
调用上面两个子脚本。执行主脚本
main.sh
,会得到下面的结果。执行函数
func1
时,变量BASH_SOURCE
的0号成员是func1
所在的脚本lib1.sh
,1号成员是主脚本main.sh
;执行函数func2
时,变量BASH_SOURCE
的0号成员是func2
所在的脚本lib2.sh
,1号成员是调用func2
的脚本lib1.sh
。BASH_LINENO
变量
BASH_LINENO
返回一个数组,内容是每一轮调用对应的行号。${BASH_LINENO[$i]}
跟${FUNCNAME[$i]}
是一一对应关系,表示${FUNCNAME[$i]}
在调用它的脚本文件${BASH_SOURCE[$i+1]}
里面的行号。下面有两个子脚本
lib1.sh
和lib2.sh
。然后,主脚本
main.sh
调用上面两个子脚本。执行主脚本
main.sh
,会得到下面的结果。函数
func1
是在main.sh
的第7行调用,函数func2
是在lib1.sh
的第8行调用的。mktemp、trap
直接创建临时文件,尤其在
/tmp
目录里面,往往会导致安全问题。首先,/tmp
目录是所有人可读写的,任何用户都可以往该目录里面写文件。创建的临时文件也是所有人可读的。其次,如果攻击者知道临时文件的文件名,他可以创建符号链接,链接到临时文件,可能导致系统运行异常。攻击者也可能向脚本提供一些恶意数据。因此,临时文件最好使用不可预测、每次都不一样的文件名,防止被利用。
最后,临时文件使用完毕,应该删除。但是,脚本意外退出时,往往会忽略清理临时文件。
生成临时文件应该遵循下面的规则。
- 创建前检查文件是否已经存在
- 确保临时文件已成功创建
- 临时文件必须有权限的限制
- 临时文件要使用不可预测的文件名
- 脚本退出时,要删除临时文件(使用trap命令)
mktemp 命令
mktemp
命令就是为安全创建临时文件而设计的。虽然在创建临时文件之前,它不会检查临时文件是否存在,但是它支持唯一文件名和清除机制,因此可以减轻安全攻击的风险。直接运行
mktemp
命令,就能生成一个临时文件。上面命令中,
mktemp
命令生成的临时文件名是随机的,而且权限是只有用户本人可读写。Bash 脚本使用
mktemp
命令的用法如下。为了确保临时文件创建成功,
mktemp
命令后面最好使用 OR 运算符(||
),保证创建失败时退出脚本。为了保证脚本退出时临时文件被删除,可以使用
trap
命令指定退出时的清除操作。mktemp 命令的参数
-d
参数可以创建一个临时目录。-p
参数可以指定临时文件所在的目录。默认是使用$TMPDIR
环境变量指定的目录,如果这个变量没设置,那么使用/tmp
目录。-t
参数可以指定临时文件的文件名模板,模板的末尾必须至少包含三个连续的X
字符,表示随机字符,建议至少使用六个X
。默认的文件名模板是tmp.
后接十个随机字符。trap 命令
trap
命令用来在Bash
脚本中响应系统信号。最常见的系统信号就是 SIGINT(中断),即按Ctrl + C
所产生的信号。trap
命令的-l
参数,可以列出所有的系统信号:trap
的命令格式如下:上面代码中,“动作”是一个 Bash 命令,“信号”常用的有以下几个:
- HUP:编号1,脚本与所在的终端脱离联系
- INT:编号2,用户按下 Ctrl + C,意图让脚本终止运行
- QUIT:编号3,用户按下 Ctrl + 斜杠,意图退出脚本
- KILL:编号9,该信号用于杀死进程
- TERM:编号15,这是kill命令发出的默认信号
- EXIT:编号0,这不是系统信号,而是 Bash 脚本特有的信号,不管什么情况,只要退出脚本就会产生。
trap
命令响应EXIT
信号的写法如下:trap
命令的常见使用场景,就是在Bash
脚本中指定退出时执行的清理命令:不管是脚本正常执行结束,还是用户按
Ctrl + C
终止,都会产生EXIT
信号,从而触发删除临时文件。注:
trap
命令必须放在脚本的开头。否则,它上方的任何命令导致脚本退出,都不会被它捕获。如果
trap
需要触发多条命令,可以封装一个Bash
函数:Bash 启动环境
用户每次使用 Shell,都会开启一个与 Shell 的 Session(对话)。
Session
有两种类型:登录Session 和非登录 Session,也可以叫做 login shell 和 non-login shell。登录 Session
登录
Session
是用户登录系统以后,系统为用户开启的原始Session
,通常需要用户输入用户名和密码进行登录。登录
Session
一般进行整个系统环境的初始化,启动的初始化脚本依次如下:/etc/profile
:所有用户的全局配置脚本
/etc/profile.d
目录里面所有.sh
文件
~/.bash_profile
:用户的个人配置脚本。如果该脚本存在,则执行完就不再往下执行。
~/.bash_login
:如果~/.bash_profile
没找到,则尝试执行这个脚本(C shell 的初始化脚本)。如果该脚本存在,则执行完就不再往下执行
~/.profile
:如果~/.bash_profile
和~/.bash_login
都没找到,则尝试读取这个脚本(Bourne shell 和 Korn shell 的初始化脚本)
Linux
发行版更新的时候,会更新/etc
里面的文件,比如/etc/profile
,因此不要直接修改这个文件。如果想修改所有用户的登陆环境,就在/etc/profile.d
目录里面新建.sh
脚本。如果想修改个人的登录环境,一般是写在
~/.bash_profile
里面。下面是一个典型的.bash_profile
文件。可以看到,这个脚本定义了一些最基本的环境变量,然后执行了
~/.bashrc
。bash
命令的--login
参数,会强制执行登录 Session 会执行的脚本。bash
命令的--noprofile
参数,会跳过上面这些 Profile 脚本。非登录 Session
非登录 Session 是用户进入系统以后,手动新建的 Session,这时不会进行环境初始化。比如,在命令行执行
bash
命令,就会新建一个非登录 Session。非登录 Session 的初始化脚本依次如下:
/etc/bash.bashrc
:对全体用户有效
~/.bashrc
:仅对当前用户有效
对用户来说,
~/.bashrc
通常是最重要的脚本。非登录 Session 默认会执行它,而登录 Session一般也会通过调用执行它。每次新建一个 Bash 窗口,就相当于新建一个非登录 Session,所以~/.bashrc
每次都会执行。注意,执行脚本相当于新建一个非互动的 Bash 环境,但是这种情况不会调用~/.bashrc
。bash
命令的--norc
参数,可以禁止在非登录 Session 执行~/.bashrc
脚本。bash
命令的--rcfile
参数,指定另一个脚本代替.bashrc
。.bash_logout
~/.bash_logout
脚本在每次退出 Session 时执行,通常用来做一些清理工作和记录工作,比如删除临时文件,记录用户在本次 Session 花费的时间。如果没有退出时要执行的命令,这个文件也可以不存在。启动选项
为了方便 Debug,有时在启动 Bash 的时候,可以加上启动参数。
-n
:不运行脚本,只检查是否有语法错误
-v
:输出每一行语句运行结果前,会先输出该行语句
-x
:每一个命令处理之前,先输出该命令,再执行该命令
键盘绑定
Bash
允许用户定义自己的快捷键。全局的键盘绑定文件默认为/etc/inputrc
,可以在主目录创建自己的键盘绑定文件.inputrc
文件。如果定义了这个文件,需要在其中加入下面这行,保证全局绑定不会被遗漏。.inputrc
文件里面的快捷键,可以像这样定义,"\C-t":"pwd\n"
表示将Ctrl + t
绑定为运行pwd
命令。命令提示符
环境变量 PS1
命令提示符通常是美元符号
$
,对于根用户则是井号#
。这个符号是环境变量PS1
决定的,查看当前命令提示符的定义:Bash 允许用户自定义命令提示符,只要改写这个变量即可。改写后的
PS1
,可以放在用户的 Bash 配置文件.bashrc
里面,以后新建 Bash 对话时,新的提示符就会生效。要在当前窗口看到修改后的提示符,可以执行下面的命令。命令提示符的定义,可以包含特殊的转义字符,表示特定内容。
\a
:响铃,计算机发出一记声音。
\d
:以星期、月、日格式表示当前日期,例如“Mon May 26”。
\h
:本机的主机名。
\H
:完整的主机名。
\j
:运行在当前 Shell 会话的工作数。
\l
:当前终端设备名。
\n
:一个换行符。
\r
:一个回车符。
\s
:Shell 的名称。
\t
:24小时制的hours:minutes:seconds
格式表示当前时间。
\T
:12小时制的当前时间。
\@
:12小时制的AM/PM
格式表示当前时间。
\A
:24小时制的hours:minutes
表示当前时间。
\u
:当前用户名。
\v
:Shell 的版本号。
\V
:Shell 的版本号和发布号。
\w
:当前的工作路径。
\W
:当前目录名。
\!
:当前命令在命令历史中的编号。
\#
:当前 shell 会话中的命令数。
\$
:普通用户显示为$
字符,根用户显示为#
字符。
\[
:非打印字符序列的开始标志。
\]
:非打印字符序列的结束标志。
举例来说,
[\u@\h \W]\$
这个提示符定义,显示出来就是[user@host ~]$
(具体的显示内容取决于你的系统)。改写
PS1
变量,就可以改变这个命令提示符。注意,
$
后面最好跟一个空格,这样的话,用户的输入与提示符就不会连在一起。颜色
默认情况下,命令提示符是显示终端预定义的颜色。
Bash
允许自定义提示符颜色。使用下面的代码,可以设定其后文本的颜色。\033[0;30m
:黑色
\033[1;30m
:深灰色
\033[0;31m
:红色
\033[1;31m
:浅红色
\033[0;32m
:绿色
\033[1;32m
:浅绿色
\033[0;33m
:棕色
\033[1;33m
:黄色
\033[0;34m
:蓝色
\033[1;34m
:浅蓝色
\033[0;35m
:粉红
\033[1;35m
:浅粉色
\033[0;36m
:青色
\033[1;36m
:浅青色
\033[0;37m
:浅灰色
\033[1;37m
:白色
如果要将提示符设为红色,可以将
PS1
设成下面的代码:但是,上面这样设置以后,用户在提示符后面输入的文本也是红色的。为了解决这个问题, 可以在结尾添加另一个特殊代码
\[\033[00m\]
,表示将其后的文本恢复到默认颜色。除了设置前景颜色,Bash 还允许设置背景颜色:
\033[0;40m
:蓝色
\033[1;44m
:黑色
\033[0;41m
:红色
\033[1;45m
:粉红
\033[0;42m
:绿色
\033[1;46m
:青色
\033[0;43m
:棕色
\033[1;47m
:浅灰色
下面是一个带有红色背景的提示符。
环境变量 PS2,PS3,PS4
除了
PS1
,Bash 还提供了提示符相关的另外三个环境变量。环境变量PS2
是命令行折行输入时系统的提示符,默认为>
。上面命令中,输入
hello
以后按下回车键,系统会提示继续输入。这时,第二行显示的提示符就是PS2
定义的>
。环境变量
PS3
是使用select
命令时,系统输入菜单的提示符。环境变量
PS4
默认为+
。它是使用 Bash 的-x
参数执行脚本时,每一行命令在执行前都会先打印出来,并且在行首出现的那个提示符。比如脚本
test.sh
:使用
-x
参数执行这个脚本:输出的第一行前面有一个
+
,这就是变量PS4
定义的。