8. 需要的支持技术
我们说到希望从atq指令的输出中,自动计算出还剩多长时间休息,而不是靠用户自己计算。atq的输出是这样的:
1 3444 Tue Nov 27 18:47:00 2012 r young
2 3445 Tue Nov 27 18:48:00 2012 r young
第1行是提醒的任务,第2行是锁屏的任务。这里已经包含了执行任务的时间,即Tue Nov 27 18:47:00
2012,我们要做的是把这段数据抽出来,跟当前时间做减法。
单纯这么一说,似乎任务很简单,但事实要更复杂一些,一会儿你会看到。在看到任务的时候就确定有哪些支持技术,是非常困难的。难到就像,你还没有迈出第一步,就确定以后的路上会遇到哪些大河要跨过,哪些高山要翻越。不知道以后会有什么困难就无从准备,而那些没有做好准备就遇到的困难,正是扼杀成功的原因。怎么办呢?无他,或者,如果我们有经验,走过一次,那就已经知道了;又或者,有个向导带着我们走,他已经走过一次;又或者,干脆现在走一次,然后就知道了。
在没走之前先猜测一下会怎么样,在走的每一步记录,这正是工程师的基本素养:估算和度量。
9. 探索的过程
我们准备写一个shell脚本,运行的时候显示出当前时刻距离atq中的第二行任务还剩多长时间
9.1 解析数据
要从
1 3444 Tue Nov 27 18:47:00 2012 r young
2 3445 Tue Nov 27 18:48:00 2012 r young
中把"Tue Nov 27 18:48:00
2012"单独取出来,是按约定的语法或格式处理字符串,在编译原理里叫作做解析。解析简单的数据的手段很多,比如sed,比如cut。
我这样实现的:
atq -q r | cut -f2 | cut -d' ' -f 1,2,3,4,5 | sort | sed -n 2p
这是由一组管理连起来的进程组成。"|"就是管道,表示前一个进程的输出作为后一个进程的输入。我们一步一步看。这些进程,包括cut,sort,sed,都是字符串处理常用工具,具体的用法和参数我也记不住,常要看手册或者google。
第一步,atq -q r,表示只列出队列r中的任务。执行效果是:
~ $ atq -q r
3502 Thu Nov 29 19:33:00 2012 r young
3503 Thu Nov 29 19:34:00 2012 r young
第二步,cut -f2,表示取出第二个字段。效果相当于下面这两行:
~ $ atq -q r | cut -f2
Thu Nov 29 19:33:00 2012 r young
Thu Nov 29 19:34:00 2012 r young
只去除了3501和3502两个序号。不要小巧这点工作,这是一个很好的开始,并且表明我们能够操作字符串了。
第三步,cut -d' ' -f
1,2,3,4,5,表示取第1、2、3、4、5字段,并且以空格作为分隔符划分字段,效果如下,切掉了后面的队列名和用户名,离目标更近了:
~ $ atq -q r | cut -f2 | cut -d' ' -f 1,2,3,4,5
Thu Nov 29 19:33:00 2012
Thu Nov 29 19:34:00 2012
第四步,sort,排序,默认是由小到大,效果如下:
~ $ atq -q r | cut -f2 | cut -d' ' -f 1,2,3,4,5 | sort
Thu Nov 29 19:33:00 2012
Thu Nov 29 19:34:00 2012
这是为了下一步有确定的输入,以便挑出正确那行。
第五步,sed -n 2p,选择输出第二行,效果如下:
~ $ atq -q r | cut -f2 | cut -d' ' -f 1,2,3,4,5 | sort | sed -n 2p
Thu Nov 29 19:34:00 2012
小结,我们由atq输出的任务
3502 Thu Nov 29 19:33:00 2012 r young
3503 Thu Nov 29 19:34:00 2012 r young
得到了将会锁屏的时间:
Thu Nov 29 19:34:00 2012
这一时间,将用于下一步的做差计算。慢着,任务的时间和当前时间做差,当前时间是什么?
当前时间可以用date获得,效果如下:
~ $ date
Thu Nov 29 19:18:20 2012
9.2 计算时间差
求时间点 Thu Nov 29 19:18:20 2012 和时间点 Thu Nov 29 19:34:00 2012
的差,当然不能把它们直接相减了事。有的同学可能想到了,可以按上面的方法解析出日期、小时、分钟、秒,然后再做减法。这也能解决,不过由于时间相减是个工程中可以想见的常用的功能,所以,应该有现成的解决方案。这也是个原则,凡是常用的功能,一定有常用的方案或者函数。至于什么是"常用的"功能,你常做小项目,就会知道。
去做,就会明白。就像,怎么成为好人,你希望别人如何对你,别人如何做你会感动,按照那个要求去做,那就是好人了,至少会被别人评价为"好人"。
时间相减这个功能的常用方案,我这样实现:
1 task=$(date -d "$task" +%s)
2 now=$(date -d "$(date)" +%s)
3 diff=$(($task-$now))
第一行中,执行了一个指令"date -d <什么东西>
+%s",这代表把<什么东西>里的时间转化为自1970年1月1日起到<什么东西>这个时间,相差了多少秒。为什么是1970年1月1日而不是1949年呢,因为大家可能还记得C语言和UNIX操作系统诞生于1969-1970年。那个<什么东西>就是"Thu
Nov 29 19:18:20 2012"这样的东西。
执行效果是这样的:
~$ date -d "Thu Nov 29 19:18:20 2012" +%s
1354187900
到底多少秒,我们也并不需要关心,一会儿能用来求差就行了。
"task=$(date -d "$task" +%s)"中代替了<什么东西>的,不是固定的时间,像"Thu Nov 29 19:18:20
2012",而是"$task",它表示一个变量,名字叫做task。这个task是这么来的:
task=$(atq -q r | cut -f2 | cut -d' ' -f 1,2,3,4,5 | sort | sed -n 2p
)
这就像在上面三行代码里,也有个变量赋值给task:
task=$(date -d "$task" +%s)
在等号右边的task的值是"Thu Nov 29 19:18:20 2012",在等号左边的task的值是上面的那个秒数 1354187900。
第2行为now赋值为"此刻"date的秒数。
2 now=$(date -d "$(date)" +%s)
第3行求差得到diff,锁屏时刻的秒数 (距1970年元旦 )和此刻的秒数 (距1970
年元量) 的差:
3 diff=$(($task-$now))
这样,我们就得到了,还有多少秒到锁屏时间,虽不够友好,但是离目标又近了一步。
9.3 格式调整
我不希望这样的结果,还剩778秒到休息锁屏时间,虽然这和12分钟58秒没什么本质区别。我希望它这样提示 12:58。
9.3.1 换算
这个好办,对60做除法,商就是分钟数,余数就是秒数。既然这么显然,那shell一定提供了这样的支持技术,果然一goolge就有。其实上面这些,有不少我忘记了或者记不清的,我也都去google了。关键在于,你得知道那东西存在,才能google。
这里[http://www.computing.net/answers/unix/remainder-operator/6820.html]有一段
: Use the % operator. Check below
: --
: 1. expr 5 / 2 gives 2 as Quotient.
: 2. expr 5 % 2 gives 1 as Remainder.
所以,我这样实现:
minute=$((diff/60))
sec=$((diff%60))
又是变量赋值。
9.3.2 条件判断
但是,还有些别的可能,比如只剩下12分5秒的时候,我不希望显示成
还剩12:5
而是希望得到:
还剩12:05
这需要条件判断。查到shell中的条件判断的语法:
if [cond]; then
fi
当然,前提还是,你得确信知道条件判断这种东西是存在的。
我这样实现:
1 if [ "$sec" -lt "10" ]; then
2 sec=0$sec
3 else
4 sec=$sec
5 fi
9.3.3 连接字符串
上面的代码中,第2行中的"0$sec",把常量字符串与变量相连接,就是直接接起来写,这个我也试了一下,如下:
$ a='hello'
$ b='world'
$ c=$a$b
$ echo $c
helloworld
9.3.4 判断空行
还有另一个可能。如果还有不到一分钟就休息了,atq的输出将只有一行,因为提醒音的那个任务已经执行完了。
我们需要判断输出有几行,或者说,我们需要判断空行。goolge得到:
[http://stackoverflow.com/questions/3061036/how-to-find-variable-is-empty-or-in-shell-script]
: In bash at least: if [[ -z "$var" ]]
:
: the command line "man test" is your friend.
最后一行对应的UNIX谚语是:男人是最好的,原文 "man is the
best"。这里的man,不是男人,而是manual的缩写,unix下的一个指令
man,用来查手册的。尽管大家都心知肚名man不是男人的意思,还是有不少unix/linux发行版中的man指令有个等同的指令,名字叫做woman。恩,而且woman和man不仅平等,而且完全一样。
我这样判断空行:
1 if [ -z "$task" ]; then
2 echo "No more than 1 minute left to have a break."
3 exit 0
4 fi
10 bug修正和功能增加
人生总是只有开始,才知道原来后面有这么多苦难;程序总是开跑才发现,原来总有bug存在。王子和公主从此以后过上无忧无虑的幸福生活,这种事只有在童话里才有。
没啥,逢山开路,遇水搭桥,碰到bug就杀掉。
10.1 排序
atq输出的时候,输出我也不知道是怎么个顺序,所以有了上面的sort指令。最初的版本中是没有的。
10.2 只加入指定的队列
虽然还没有出现这样的情况,但是在其他的时候,或者其他的程序,可能也要使用at任务的队列。所以,我改成只选其中一个队列使用。
atq -q r,只显示队列r中的任务。
at ... -q r,向队列r中添加任务。
11. 秘密
表面上看,上面是演示如何实现西红柿时间管理。没错,西红柿时间管理确实很有效,我常用。但是,正如钢琴老师教你弹的那些小曲子也确实挺好听的,但是她教你弹曲子的目的绝对不仅是欣赏这些曲子本身,而是更注重练习曲目时所得到的能力提升。这个小项目也是一样。
以上这些步骤看起来很流畅,先这样,再那样,然后又如何,最后问题解决了。等到你实践的时候可能略有不同。这种差异的来源是因为:我其实不是盲目地探索的,因为学习过其他的语言、做过其他的小项目,所以能够猜到应该有这样的那样的解决方案存在,所以我是有意去寻找每一步的技术和有计划地逼近终点的。换句话说,我经常地猜到了那里应该有这样的技术存在,然后才去google。而google并不能告诉你你不知道的东西。
如同《怎样解题:数学思维的新方法》[http://book.douban.com/subject/2124114/]这本书所说的,教师解题的过程,并不是盲目的,而是假设或计划将会如何,然后朝着那个方向推理和努力。我们作为学生需要体会的,是在观摩教师解题过程中的那些节点和方向。然后可能还要思考,他是怎么知道到这里就会有这个技术了呢?就像故事里讲的,孩子问雕塑师,你先前怎么知道石头里有个女人的呢。
--------------------
博客会手工同步到以下地址: