执行环境
Shell 脚本执行环境
在写脚本时,在一开始(
$ set -xeuo pipefail
这能避免很多问题,更重要的是能让很多隐藏的问题暴露出来。
异常处理
在set -e
可以设置在有命令失败时候退出,我们还可以通过添加
# 脚本执行配置: https://intoli.com/blog/exit-on-errors-in-bash-scripts/
set -e
# keep track of the last executed command
trap 'last_command=$current_command; current_command=$BASH_COMMAND;' DEBUG
# echo an error message before exiting
trap '
err_code=$?;
err_command=${last_command};
if [ "$err_code" != "0" ]; then
echo "\"${err_command}\" 命令异常退出 $err_code."
fi
' EXIT
我们也可以指定在某个语句失败的时候输出错误:
exit_on_error() {
exit_code=$1
last_command=${@:2}
if [ $exit_code -ne 0 ]; then
>&2 echo "\"${last_command}\" command failed with exit code ${exit_code}."
exit $exit_code
fi
}
# enable !! command completion
set -o history -o histexpand
rm -rf $MYDIR/data
,如果rm -rf /data
,使用
但有时候在已经设置了${SOME_VAR:-}
。还有一种情况是在管道执行中,我们可以设置如果管道的某个命令出错则直接抛出异常:
set -o pipefail
防止重叠运行
在一些场景中,我们通常不希望一个脚本有多个实例在同时运行。比如用
用法
# flock --wait 超时时间 -e 锁文件 -c "要执行的命令"
# 例如:
flock --wait 5 -e "lock_myscript" -c "bash myscript.sh"
用法
exec 123<>lock_myscript # 把lock_myscript打开为文件描述符123
flock --wait 5 123 || { echo 'cannot get lock, exit'; exit 1; }
脚本调试
调试模式
我们可以在执行脚本的时候添加
$ bash -x script-name
$ bash -xv script-name
#!/bin/bash -x
echo "Hello ${LOGNAME}"
echo "Today is $(date)"
echo "Users currently on the machine, and their processes:"
w
set 指令
- set -x:在执行命令时显示命令及其参数。
- set -v:显示读取的外壳程序输入行。
- set -n:读取命令,但不执行。这可用于检查
shell 脚本中的语法错误。
#!/bin/bash
### Turn on debug mode ###
set -x
# Run shell commands
echo "Hello $(LOGNAME)"
echo "Today is $(date)"
echo "Users currently on the machine, and their processes:"
### Turn OFF debug mode ###
set +x
# Add more commands without debug mode
另一个例子如下:
#!/bin/bash
set -n # only read command but do not execute them
set -o noexec
echo "This is a test"
# no file is created as bash will only read commands but do not executes them
>/tmp/debug.txt
连续管道日志
有时候我们会用到把好多条命令用管道串在一起的情况。如 cmd1 | cmd2 | cmd3 | ...
这样会让问题变得难以排查,因为中间数据我们都看不到。如果改成这样的格式:
cmd1 > out1.dat
cat out1 | cmd2 > out2.dat
cat out2 | cmd3 > out3.dat
性能又不太好,因为这样
cmd1 | tee out1.dat | cmd2 | tee out2.dat | cmd3 > out3.dat
其他技巧
意外退出时杀掉所有子进程
我们的脚本通常会启动好多子脚本和子进程,当父脚本意外退出时,子进程其实并不会退出,而是继续运行着。如果脚本是周期性运行的,有可能发生一些意想不到的问题。在
trap "trap - SIGTERM && kill -- -\$\$" SIGINT SIGTERM EXIT
不过如果父进程是用
timeout 限制运行时间
有时候需要对命令设置一个超时时间。这时可以使用
timeout 600s some_command arg1 arg2
命令在超时时间内运行结束时,返回码为
后台执行
在作业控制设备的影响下的过程称为作业。每个作业都有一个唯一的
- fg - Place job in the foreground.
- bg - Place job in the background.
- jobs - Lists the active jobs on screen.
非连续调度的命令称为后台进程。您无法在屏幕上看到后台进程。例如,
command &
command arg1 arg2 &
command1 | command2 arg1 &
command1 | command2 arg1 > output &
& 运算符将命令放在后台,并释放终端。在后台运行的命令称为作业。您可以在后台命令运行时键入其他命令。
$ find /nas -name "*.mp3" > /tmp/filelist.txt &
# [1] 1307
[1]+ Done find /share/ -name "*.mp3" > /tmp/filelist
Subshell
每当您运行
echo $BASH_SUBSHELL
echo "Current shell: $BASH_SUBSHELL"; ( echo "Running du in subshell: $BASH_SUBSHELL" ;cd /tmp; du 2>/tmp/error 1>/tmp/output)
子
WWWJAIL=/apache.jail
export WWWJAIL
die() { echo "$@"; exit 2; }
export -f die
# now call script that will access die() and $WWWJAIL
/etc/nixcraft/setupjail -d cyberciti.com
但是,环境变量(例如
exec command
# redirect the shells stderr to null
exec 2>/dev/null
The . (dot) Command and Subshell
. script.sh
#!/bin/bash
echo "In script before : $WWWJAIL"
WWWJAIL=/apache.jail
echo "In script after : $WWWJAIL"
关闭并保存文件。如下运行:
chmod +x /tmp/dottest.sh
现在,在
WWWJAIL=/foobar
echo $WWWJAIL
样本输出:
/foobar
运行脚本:
/tmp/dottest.sh
检查
echo $WWWJAIL
您应该看到
. /tmp/dottest.sh
echo $WWWJAIL
Sample outputs:
/apache.jail
$WWWJAIL(/apache.jail)的值已更改,因为使用