Work the Shell - 更多日期和时间相关的乐趣

作者:Dave Taylor

我收到了一位读者非常有趣的笔记——一个关于一个非常有趣的问题的笔记

许多 UNIX 命令(例如,last)和日志文件显示了非常糟糕的日期字符串,例如“Thu Feb 24”。有人能提供一个脚本,在给定五年间隔并默认为当前年份的情况下,将其转换为年份吗?

给定星期几、月份和日期,是否可以快速计算出过去最近一次该特定日期出现在该星期几的年份? 当然可以!

存在各种公式来计算这类事情,但我很快意识到方便的 cal 实用程序可以为我们完成这项工作。 如果您还没有尝试过,您会惊讶于它可以做什么。 这里有两个快速相关的例子

$ cal
     March 2011
Su Mo Tu We Th Fr Sa
      1  2  3  4  5
6  7  8  9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31 

$ cal mar 2007
     March 2007
Su Mo Tu We Th Fr Sa
            1  2  3
4  5  6  7  8  9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

您是否开始有所启发? 如果您知道月份和日期,您可以简单地向后查看该月份的星期几布局,直到最终找到匹配项。

以一种基本的方式,基本思想可以用一个循环来说明,像这样

repeat
  cal $month $year | grep $day
  if day-of-week matches
     echo date $month $day most recently occurred in $year
  else
     year=$(( $year - 1 ))
end repeat

当然,这个问题有点复杂(总是如此),部分原因是计算特定日期出现在 cal 输出中的星期几的复杂性。 然而,还有另一个复杂之处; 请求的日期实际上可能发生在今年,因此不像从 2010 年开始向后推那么简单。

规范化数据

首要任务是弄清楚如何从用户那里获取信息。 我们将只有三个输入参数,并且对拼写错误的日期名称等进行相对较少的测试

if [ $# -ne 3 ] ; then
  echo "Usage: $(basename $0) weekday month day"
  echo "  (example: $(basename $0) wed aug 3  )"
  exit 1
fi

这很直接也很典型,如果您忘记了如何使用脚本,可以提供一个很好的使用技巧。 与脚本的典型情况一样,我们在出错时也会返回非零结果。

但是,我们无法处理完全任意的数据,因此,当我们获取前几个参数时,我们会将它们音译为小写,并删除除前三个字母以外的所有字母

weekday=$(echo $1 | tr '[[:upper:]]' '[[:lower:]]'; | cut -c1-3)
  month=$(echo $2 | tr '[[:upper:]]' '[[:lower:]]'; | cut -c1-3)
    day=$3

给定“Monday, February 8”,它将被自动转换为“mon”和“feb”以供后续测试。

当前日期

我们还需要当前日期字段进行测试,为此,我将演示一个非常巧妙的技巧date这使得它非常高效

eval $(date "+thismonth=%m; thisday=%d; thisyear=%Y")

这个eval函数将其参数解释为好像它是一个直接的脚本命令。 更有趣的是,date可以输出任意格式(如strftime的文档中所述,如果您想阅读手册页),通过使用 + 前缀,其中 %m 是月份数字,%d 是月份中的日期,%Y 是年份。 结果是date创建字符串

thismonth=03; thisday=01; thisyear=2011

然后由 shell 解释以创建和实例化三个命名变量。 很巧妙,不是吗?

事实证明,用户可以在命令行中按名称或数字指定月份。 如果它已经是一个数字,它将在转换中完整无损地保留下来。 但如果它是一个名称,我们也需要数字,这样我们就可以确定指定的日期是否可能早于今年。 有几种方法可以做到这一点,包括 case 语句,但这需要做很多工作。 相反,我将像我经常做的那样依赖 sed

monthnum=$(echo $month | sed
's/jan/1/;s/feb/2/;s/mar/3/;s/apr/4/;s/may/5/;s/jun/
↪6/;s/jul/7/;s/aug/8/;s/sep/9/;s/oct/10/;s/
↪nov/11/;s/dec/12/')

这里拼写错误的月份名称是一个问题,但这超出了本脚本的范围。 然而,目前,我们将继续使用它。

日期可能发生在今年吗?

下一组测试是我重写了几次以确保我没有自找麻烦的测试,因为我的第一个想法只是使用像这样的测试

if [ $month -le $thismonth -a $day -le $thisday ]

但是,后来我意识到在边缘情况下它实际上无法正常工作。 例如,假设现在是 4 月 4 日,您正在检查 3 月 11 日。 月份测试成功,但日期测试失败——这不是我们想要的。 相反,让我们使用一系列级联的条件测试

if [ $monthnum -gt $thismonth ] ; then
  # month is in the future, can't be this year
  mostrecent=$(( $thisyear - 1 ))
elif [ $monthnum -eq $thismonth -a $day -gt $thisday ] ; then
  # right month, but seeking a date in the future
  mostrecent=$(( $thisyear - 1 ))
else
  mostrecent=$thisyear
fi

仅使用这么多代码,我们至少可以测试数据输入和比较工具的规范化。 顺便说一句,我在 3 月 1 日运行了这组测试

$ whatyear.sh Monday Aug 3
Decided that for 8/3 we're looking at year 2010
$ sh whatyear.sh mon jan 9
Decided that for 1/9 we're looking at year 2011
$ whatyear.sh mon mar 1
Decided that for 3/1 we're looking at year 2011
$ whatyear.sh mon mar 2
Decided that for 3/2 we're looking at year 2010

它正确地识别出当前日期可能是匹配项,但随后的日期(3 月 2 日)必须在前一年才有可能。

好的。 下个月,我们将把剩余的乐高积木放入模型中,并获得一个可用的脚本。 剩下的最大任务是什么? 解析 cal 的输出以找出给定日期的星期几。

Dave Taylor 从事 shell 脚本编写已经很长时间了,30 年。 他是流行的Wicked Cool Shell Scripts的作者,可以在 Twitter 上找到他 @DaveTaylor,更普遍地可以在 www.DaveTaylorOnline.com 上找到他。

加载 Disqus 评论