编写命令行shell脚本时,总是免不了处理当前终端的相关信息,比如行数、列数、光标位置、遮盖的密码字段等。tput和stty是两款终端处理工具。
1、获取终端信息
a. tput 命令
获取终端行数 | tput cols |
获取终端列数 | tput lines |
打印当前的终端名 | tput longname |
将光标移动到坐标(100,100)处 | tput cup 100 100 |
设置终端背景色n∈[0,7] | tput setb n |
设置文本样式为粗体 | tput bold |
设置下划线的起点位置 | tput smul |
设置下划线的终止位置 | tput rmul |
删除从当前光标位置到行尾的所有内容: | tput ed |
b. stty 命令
输入密码时,脚本不应该显示输入内容。在下面的例子中,我们将看到如何使用stty来实现这一需求:
#!/bin/sh
#Filename: password.sh
echo -e "Enter password: "
# 在读取密码前禁止回显
stty -echo # 禁止将输出发送到终端
read password
# 重新允许回显
stty echo # 允许输出发送到终端
echo
echo Password read.
注意:stty命令的选项-echo禁止将输出发送到终端,而选项echo则允许发送输出。
2、获取并设置日期
date 命令
延时可以用来在程序执行过程中等待一段时间(比如1秒),或是每隔几秒钟(或是几个月)监督某项任务。与时间和日期打交道需要理解如何描述并处理这两者。
日期能够以多种格式呈现。在系统内部,日期被存储成一个整数,其取值为自1970年1月1日0时0分0秒起所流逝的秒数。这种计时方式称为纪元时或Unix时间。可以在命令行中设置系统日期:
# 读取日期
date
#打印纪元时
date +%s
# 将日期转换为纪元时
date --date "Web mar 15 08:09:16 EDT 2017" +%s
# 根据指定的日期找出这一天是星期几
date --date "Jan 20 2001" +%A
# 用带有前缀+的格式化字符串作为date命令的参数,自定义打印格式
date "+%d %B %Y" # 输出:日 月 年
# 设置日期和时间
date -s "格式化日期字符串"
date -s "21 June 2009 11:01:22" # Sun Jun 21 11:01:22 CST 2009
要优化代码,首先得先进行测量。date命令可以用于计算一组命令所花费的执行时间:
start=$(date +%s)
......
......
end=$(date +%s)
difference=$((end - start))
echo Time taken to execute commands is $difference seconds.
- Unix纪元时被定义为从世界标准时间(Coordinated Universal Time,UTC)1970年1月1日0时0分0秒起至当前时刻的总秒数,不包括闰秒。
- 当计算两个日期或两段时间的差值时,需要用到纪元时。将两个日期转换成纪元时并计算出两者之间的差值。
3、获取并设置延时
编写以循环方式运行的监控脚本时,设置时间间隔是必不可少的。
在脚本中生成延时:sleep命令可以延迟脚本执行一段时间(以秒为单位)。下面的脚本使用tput和sleep从0开始计时到40秒:
#!/bin/bash
#filename: sleep.sh
echo Count:
tput sc
# 循环40秒
for count in `seq 0 40`
do
tput rc
tput ed
echo -n $count
sleep 1
done
- 我们用tput sc存储光标位置。
- 变量依次使用了由seq命令生成的一系列数字。
- 在每次循环中,通过tput rc恢复之前存储的光标位置,在终端中打印出新的count值,然后使用tputs ed清除从当前光标位置到行尾之间的所有内容。行被清空之后,脚本就可以显示出新的值。
- sleep可以使脚本在每次循环迭代之间延迟1秒钟。
4、脚本调试
调试脚本所花费的时间常常比编写代码还要多。所有编程语言都应该实现的一个特性就是在出现始料未及的情况时,能够生成跟踪信息。调试信息可以帮你弄清楚是什么原因使得程序行为异常。每位系统程序员都应该了解Bash提供的调试选项。我们可以利用Bash内建的调试工具或者按照易于调试的方式编写脚本
-x选项会输出脚本中执行过的每一行。不过,我们可能只关注其中某一部分代码。针对这种情况,可以在脚本中使用set builtin来启用或禁止调试打印。
set -x | 在执行时显示参数和命令。 |
set +x | 禁止调试 |
set -v | 当命令进行读取时显示输入。 |
set +v | 禁止打印输入 |
使用选项-x,启用shell脚本的跟踪调试功能:bash -x myScripts.sh
使用set -x和set +x对脚本进行部分调试。
#!/bin/bash
# filename: debug.sh
for i in {1..6};
do
set -x
echo $i
set +x
done
echo "Script executed"
- 在上面的脚本中,只会打印出echo $i的调试信息,因为使用-x和+x对调试区域进行了限制。
- 该脚本并没有使用上例中的seq命令,而是用{start..end}来迭代从start到end之间的值。这个语言构件(construct)在执行速度上要比seq命令略快。
前面介绍的调试方法是Bash内建的。它们以固定的格式生成调试信息。但是在很多情况下,我们需要使用自定义的调试信息。可以通过定义 _DEBUG环境变量来启用或禁止调试及生成特定形式的信息。
#!/bin/bash
function DEBUG()
{
[ "$_DEBUG" == "on" ] && $@ || :
}
for i in {1..10}
do
DEBUG echo "I is $i"
done
可以将调试功能设置为on来运行上面的脚本:
$ _DEBUG=on ./script.sh
- 每一条需要打印调试信息的语句前加上DEBUG。如果没有把 _DEBUG=on传递给脚本,那么调试信息就不会打印出来。
利用shebang来进行调试:
把shebang从#!/bin/bash改成 #!/bin/bash -xv,这样一来,不用任何其他选项就可以启用调试功能了。
如果每一行前面都加上+,那么就很难在默认输出中跟踪执行流程了。可以将环境变量PS4设置为'$LINENO:',显示出每行的行号:PS4='$LINENO:'
调试的输出信息可能会很长。如果使用了-x或set -x,调试输出会被发送到stderr。可以使用下面的命令将其重定向到文件中:sh -x testScript.sh 2> debugout.txt
Bash 4.0以及后续版本支持对调试输出使用自定义文件描述符:
exec 6> /tmp/debugout.txt
BASH_XTRACEFD=