alarm.awk 分析

gawk 示例脚本 alarm.awk 代码分析

gawk 示例脚本 alarm.awk 代码

# alarm.awk --- set an alarm
#
# Requires getlocaltime() library function
#
# Arnold Robbins, arnold@skeeve.com, Public Domain
# May 1993
# Revised December 2010

# usage: alarm time [ "message" [ count [ delay ] ] ]

BEGIN {
    # Initial argument sanity checking
    usage1 = "usage: alarm time ['message' [count [delay]]]"
    usage2 = sprintf("\t(%s) time ::= hh:mm", ARGV[1])

    if (ARGC < 2) {
        print usage1 > "/dev/stderr"
        print usage2 > "/dev/stderr"
        exit 1
    }
    switch (ARGC) {
    case 5:
        delay = ARGV[4] + 0
        # fall through
    case 4:
        count = ARGV[3] + 0
        # fall through
    case 3:
        message = ARGV[2]
        break
    default:
        if (ARGV[1] !~ /[[:digit:]]?[[:digit:]]:[[:digit:]]{2}/) {
            print usage1 > "/dev/stderr"
            print usage2 > "/dev/stderr"
            exit 1
        }
        break
    }

    # set defaults for once we reach the desired time
    if (delay == 0)
        delay = 180    # 3 minutes
    if (count == 0)
        count = 5
    if (message == "")
        message = sprintf("\aIt is now %s!\a", ARGV[1])
    else if (index(message, "\a") == 0)
        message = "\a" message "\a"
    # split up alarm time
    split(ARGV[1], atime, ":")
    hour = atime[1] + 0    # force numeric
    minute = atime[2] + 0  # force numeric

    # get current broken down time
    getlocaltime(now)

    # if time given is 12-hour hours and it's after that
    # hour, e.g., `alarm 5:30' at 9 a.m. means 5:30 p.m.,
    # then add 12 to real hour
    if (hour < 12 && now["hour"] > hour)
        hour += 12

    # set target time in seconds since midnight
    target = (hour * 60 * 60) + (minute * 60)

    # get current time in seconds since midnight
    current = (now["hour"] * 60 * 60) + \
               (now["minute"] * 60) + now["second"]

    # how long to sleep for
    naptime = target - current
    if (naptime <= 0) {
        print "alarm: time is in the past!" > "/dev/stderr"
        exit 1
    }
    # zzzzzz..... go away if interrupted
    if (system(sprintf("sleep %d", naptime)) != 0)
        exit 1

    # time to notify!
    command = sprintf("sleep %d", delay)
    for (i = 1; i <= count; i++) {
        print message
        # if sleep command interrupted, go away
        if (system(command) != 0)
            break
    }

    exit 0
}

alarm.awk 如何使用

alarm.awk 用法如下:

usage: alarm time [ "message" [ count [ delay ] ] ]

注意中括号中为可选参数,time 的格式为 hh:mm。

从命令行中获取参数的注意事项

对于需要从命令行中获取参数的脚本,首先要检查参数个数是否足够,然后要检查参数格式是否正确。

对参数个数的检查可以通过判断 ARGC 的值来完成,对参数格式的检查可以通过正则表达式匹配来完成。

当检查到用户传入的参数不合法时,需要打印使用信息,以供用户参考。对于将要打印的使用信息或者错误信息,可以提前制作好。当检测到错误时,就打印出制作好的信息,这样就将错误信息与具体的异常处理过程分隔开,修改错误信息将不会影响异常处理的代码。也许当仅仅有一个异常处理流程时,上述操作的意义并不大,当数目上升后,修改一次错误信息意味着要修改多个实例,无疑增加了错误产生的几率。

判断参数个数与参数合法性的顺序也要注意。当参数个数都不合法时,在此之上检查参数合法性就失去了意义,毕竟你无法从一个错误的前提推出一个正确的结论。所以,首先检查参数个数,不合法则终止程序,合法则继续检查参数格式。

alarm.awk 中对参数的检验与使用

alarm.awk 中在检查参数格式合法性的同时也按照参数生成初始化配置。注意命令行传给 awk 的参数都是字符串,对 message 参数不需要进行任何进一步的处理,对于整型变量,却要进行转化。

转化方式如下:

      delay = ARGV[4] + 0
      count = ARGV[3] + 0

对于 time 的校验通过以下代码完成:

     if (ARGV[1] !~ /[[:digit:]]?[[:digit:]]:[[:digit:]]{2}/)

[:digit:] 为字符类的一种。字符类仅仅在一个正则表达式中的字符列表中有效,上面将所有的字符类都包在字符列表之中,符合字符类的使用要求。

? 表示最多匹配一次或者不匹配,{2} 表示重复匹配两次。

上述正则表达式检查的字符串格式如下:

[h]h:mm

switch 中的注意事项

在 switch 语句中需要注意没有加 break 的 case 项将继续向下执行!

    case 5:
        delay = ARGV[4] + 0
        # fall through
    case 4:
        count = ARGV[3] + 0
        # fall through
    case 3:
        message = ARGV[2]
        break

alarm.awk 提供的默认配置

由于 message 、delay、count 都是可选参数,当用户没有指定时,应该设置为默认值。

delay 默认值为 180,表示时间到了之后每 3 分钟提醒一次。count 默认值为 5,表示总共提醒 5 次。message 默认值使用 snprintf 制作,snprintf 将会返回一个字符串,而不是直接输出到 stdout 中。

代码如下:

	# set defaults for once we reach the desired time
	    if (delay == 0)
	        delay = 180    # 3 minutes
	    if (count == 0)
	        count = 5
	    if (message == "")
	        message = sprintf("\aIt is now %s!\a", ARGV[1])
	    else if (index(message, "\a") == 0)
	        message = "\a" message "\a"

获取时间时字符串与数字的转化

在此之后,使用 split 以冒号为分割符分割时间字符串,将结果存储到数组 atime 中。以切割后的小时与分钟设置 hour 与 minute 变量,这里需要将字符串转化为数字。这部分的代码如下:

	  # split up alarm time
	    split(ARGV[1], atime, ":")
	    hour = atime[1] + 0    # force numeric
	    minute = atime[2] + 0  # force numeric

alarm.awk 中如何处理时间

alarm 的时间需要以当前时间为基准来比较,当前时间如何生成是现在需要解决的问题。查看 gawk 的 manual 发现,可以通过调用 strftime 来按照指定的格式生成时间戳,也可以通过 mktime 来生成时间戳。注意这两个函数都需要以 systime 函数的返回值为参数来制作时间戳。

alarm.awk 中使用了 getlocaltime。这个函数并不是 gawk 内置的函数,在查看 lib 目录后,我发现了它的定义如下:


	function getlocaltime(time,    ret, now, i)
	{
	    # get time once, avoids unnecessary system calls
	    now = systime()
	
	    # return date(1)-style output
	    ret = strftime("%a %b %e %H:%M:%S %Z %Y", now)
	
	    # clear out target array
	    delete time
	
	    # fill in values, force numeric values to be
	    # numeric by adding 0
	    time["second"]       = strftime("%S", now) + 0
	    time["minute"]       = strftime("%M", now) + 0
	    time["hour"]         = strftime("%H", now) + 0
	    time["althour"]      = strftime("%I", now) + 0
	    time["monthday"]     = strftime("%d", now) + 0
	    time["month"]        = strftime("%m", now) + 0
	    time["monthname"]    = strftime("%B", now)
	    time["shortmonth"]   = strftime("%b", now)
	    time["year"]         = strftime("%y", now) + 0
	    time["fullyear"]     = strftime("%Y", now) + 0
	    time["weekday"]      = strftime("%w", now) + 0
	    time["altweekday"]   = strftime("%u", now) + 0
	    time["dayname"]      = strftime("%A", now)
	    time["shortdayname"] = strftime("%a", now)
	    time["yearday"]      = strftime("%j", now) + 0
	    time["timezone"]     = strftime("%Z", now)
	    time["ampm"]         = strftime("%p", now)
	    time["weeknum"]      = strftime("%U", now) + 0
	    time["altweeknum"]   = strftime("%W", now) + 0
	
	    return ret
	}

getlocaltime 使用 strftime 转化 systime 生成的 Epoch 时间,将结果填充到 hash 数组 time 中,这里同样使用了将字符串转化为数字的方法。与此同时此函数也制作了类似于 date 命令输出风格的时间戳格式作为返回值。

如何在其它源文件中使用 getlocaltime 函数

可以使用 @include 语句将该源文件包含进当前源文件即可。

格式如下:

@include filename

也可以在命令行中使用 -i 参数来指定源文件。

与 alarm 相关的代码逻辑

生成了当前时间之后,将用户输入的时间与当前时间做比较,根据比较的结果进行补偿,计算出需要延时的时间 naptime。如果计算出的延时时间小于等于 0,表示时间已经过去,输出错误信息,终止程序,返回值为 1 表示产生了错误。

如果 naptime 大于 0 ,使用 system 函数来调用系统中的 sleep 命令进行延时操作,延时过程中出现错误则终止程序。

延时时间结束后需要通知用户。如果用户指定了 count ,则会以 3 分钟为间隔进行 count 次提醒后自动退出。如果在 3 分钟的延时中接收到了中断信号则跳出循环,终止程序,此时的返回值为 0 表示正常终止。

测试示例

	[longyu@debian:21:59:11] prog $ awk -i ../lib/gettime.awk -f alarm.awk  
	usage: alarm time ['message' [count [delay]]]
		() time ::= hh:mm

	[longyu@debian:21:56:23] prog $ awk -i ../lib/gettime.awk -f alarm.awk   21:55 "time is coming!" 5  1
	time is in the past!

	[longyu@debian:21:57:01] prog $ awk -i ../lib/gettime.awk -f alarm.awk   21:58 "time is coming!" 5  1
	time is coming!
	time is coming!
	time is coming!
	time is coming!
	time is coming!

功能扩展

这个程序类似于手机上的闹钟!message 中添加的 ‘\a’ 在我的系统上没有发出任何警示音,倒是我通过 nvlc 完成了以音乐通知的操作,我或许有几年没有使用过 nvlc 这个命令了,唯一要感谢的是 man -k vlc,没有它我就免不了百度、谷歌一下了,其实我大可不必这样做!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值