如何写一个命令行的秒表

55次阅读

共计 2013 个字符,预计需要花费 6 分钟才能阅读完成。

序言

相信各位读者对秒表都不陌生,智能手机上通常都有这样一款软件

有一天心血来潮,便想要“复刻”一个命令行版本的秒表程序——主要是想尝试一下新学会的、“原地更新”的技能,而不是一行接一行地输出。程序的运行效果如下

那么这是怎么做的呢?

实现思路及代码

如何获取流逝的时间长度?

要实现一个秒表,首先要知道从开始计时至今过了多久。在 *nix 系统中,表示时刻的事实标准是 Epoch Time,在 shell 脚本中要获取 Epoch Time 可以用 date 命令。再用首尾时刻相减便得到了期间流逝的秒数了,示例代码如下

begin_at=$(date '+%s')
# 睡个觉
end_at=$(date '+%s')
((interval=${end_at} - ${begin_at}))

双圆括号是一种在 shell 脚本中执行算术运算的语法,其它语法可以参见 Math in Shell Scripts。

如何换算为时分秒?

有了 interval 中存储的总秒数后,换算成时分秒便是轻而易举的事情,示例代码如下

((hours=${interval} / 3600))
((minutes=(${interval} % 3600) / 60))
((seconds=(${interval} % 3600) % 60))

如何输出形如 hh:mm:ss 的格式?

hh:mm:ss的意思是分别用两个十进制数字显示时分秒,并以冒号分隔它们。如果有任何一个单位的数值小于 10,便用字符 0 填充左侧的空白。按这个格式,凌晨 1 点 2 分 3 秒便会显示为01:02:03

要在命令行中打印字符串,最容易想到的便是 echo 命令,只可惜它不能方便地实现填充字符 0 的需求。

强人所难也不是不行,示例代码如下

hours=1
minutes=2
seconds=3

if ["${hours}" -lt '10' ];
then
   echo -n "0${hours}"
else
   echo -n "${hours}"
fi
echo -n ':'
if ["${minutes}" -lt '10' ];
then
   echo -n "0${minutes}"
else
   echo -n "${minutes}"
fi
echo -n ':'
if ["${seconds}" -lt '10' ];
then
   echo -n "0${seconds}"
else
   echo -n "${seconds}"
fi

更优雅的方法是用 printf 命令来自动填充左侧的字符0

printf "%02d:%02d:%02d" ${hours} ${minutes} ${seconds}

printf命令类似于 C 语言中的 printf 函数——它也支持打印转义的字符,下文会提到。

如何覆盖已经打印的内容?

今年以来我在断断续续地看 Build Your Own Text Editor,学习如何开发文本编辑器。在这本小册子的第三章中,作者讲述了如何使用终端的转义序列(escape sequence)来控制屏幕上显示的东西——这正是秒表程序所需要的。

例如,在终端输出转义序列 \x1b[2J 可以清空屏幕,效果如下

为了覆盖已经打印出来的时分秒,需要:

  1. 先将光标移动到行首;
  2. 再清除从光标开始到行末的内容。

查阅《VT100 User Guide》第三章可以知道

  1. 要把光标移动到行首可以用转义序列 \x1b[8D。之所以是 8,是因为按照hh:mm:ss 输出时分秒后光标距离行首 8 个身位;
  2. 要清除光标到行末内容可以用转义序列\x1b[0K(实际上,将光标移到行首只需要使用回车(carriage return)即可,但它被解释为开启新的一行了)。

更优雅的方法甚至连转义序列也不需要,只要用 tput 命令即可,示例代码如下

echo -n '11:22:33'
tput cr
tput el
echo '44:55:66'

关于 crel,以及更多可以传给 tput 命令的参数,可以参见 terminfoman文档。

如何每隔一秒钟输出一次?

这大概是整个程序中最简单的需求了

while [1 -eq 1]
do
    # 此处可以为所欲为
    sleep 0.5
done

完整的秒表实现

至此,完整的秒表程序就可以实现出来了

#!/bin/bash
# 秒表,以 hh:mm:ss 的格式展示数据

begin_at=$(date '+%s')

while [1 -eq 1]
do
    end_at=$(date '+%s')
    # 算术运算:http://faculty.salina.k-state.edu/tim/unix_sg/bash/math.html
    ((interval=${end_at} - ${begin_at}))
    ((hours=${interval} / 3600))
    ((minutes=(${interval} % 3600) / 60))
    ((seconds=(${interval} % 3600) % 60))
    tput cr
    tput el
    printf "%02d:%02d:%02d" ${hours} ${minutes} ${seconds}
    sleep 0.5
done

运行后的效果正如本文开头的 GIF 所示。

全文完。

正文完
 0