Shell 脚本应用 - 如何知道过去某天是星期几?
在之前的一篇文章中,我们开始编写一个脚本,该脚本从给定的日期(日和月)倒推,找出最近的年份(可能包括当年),该年份的日期与给定的星期几相匹配。例如,4 月 1 日是星期五,最近一次是今年,即 2011 年,但 4 月 1 日是星期二呢?上次发生这种情况是什么时候?
为了增加趣味性,我们的脚本专注于利用 Linux 中一个不为人知的实用工具 `cal`,并解析其输出来识别给定日期的星期几。
正如 shell 脚本的典型情况一样,到目前为止,大部分工作都涉及规范化输入数据,以便我们传递给 `cal` 程序的内容能够正常工作并被程序理解。
然而,更大的挑战是弄清楚可能的日期是否可能在今年。由于程序始终向后查找,因此它需要知道当前日期才能进行比较。也就是说,我现在正在 2011 年 4 月 3 日写这篇文章。如果我检查最近一次 4 月 1 日是星期五,它应该显示 2011 年,但如果我检查最近一次 5 月 1 日是星期日,它不应该建议 2011 年。那是在未来,不是一个有效的答案。
这些都在我之前的专栏中展示过,所以让我们继续讨论一些新的内容:弄清楚如何解析 `cal` 的输出。
解析 `cal` 日历对于任何给定的月份和年份,`cal` 都会产生类似于以下的输出
August 2008
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
假设我们要查找 8 月 3 日。为了在此输出中搜索它,我们需要指定日期前后不应有数字。这可以使用简单的正则表达式来完成
$ cal aug 2008 | grep -e '[^0-9]3[^0-9]'
3 4 5 6 7 8 9
(正如您稍后将了解到的,作为正则表达式,这并不充分。如果您真的在注意,您已经怀疑它会变得更加复杂。)
现在,我们需要弄清楚哪个数字匹配。
求助 `awk`我们将要使用的基本方法是让 `awk` 使用 `for` 循环遍历匹配指定模式的行上的每个字段
{ for (i=1;i<=NF;i++) if ($i~/regex/) print i}
我们可以将此方法与上面的 `grep` 语句一起使用,但让我们通过让 `awk` 也进行条件测试来节省一个命令
$ cal aug 2008 | awk -e '/regex/ { for (i=1;i<=NF;i++)
if ($i~/regex/ print i }'
为了测试这一点,让我们使用一个正则表达式来测试该月的 5 号
[^0-9]5[^0-9]
这种方法在某种程度上有效,但存在一个问题。如果我们搜索 10 号,因为它出现在行首,所以它不匹配正则表达式片段
[^0-9]10[^0-9]|^10[^0-9]|[^0-9]10$
`|` 是逻辑“或”语句,所以它现在是较早的表达式或一个具有我们要查找的模式,后跟非数字,但在行首(单独的 `^`)或是以非数字开头的模式,位于行尾(`$` 符号)。
幸运的是,我们正在编写脚本,所以我们不必多次键入它。太好了!
此输出中还有另一个细节。我们不仅需要知道匹配的数字出现在哪个字段中,还需要知道匹配行中总共有多少个字段。为什么?否则,上面星期一出现的数字 2 看起来与星期六出现的数字 2 完全一样。
这是我们到目前为止的测试脚本片段
expr="[^0-9]${day}[^0-9]|^${day}[^0-9]|[^0-9]${day}\$"
cal aug 2008 | awk "/$expr/ { print \$0 }"
请注意,我们需要使用双引号,以便扩展变量 `$day`,然后也扩展 `$expr`,这意味着我们还需要转义此测试中的 `$0`。
但这并不是我们想要的。`awk` 语句需要更复杂,因为我们想知道匹配的字段号(例如,星期几 1-7)以及匹配行中的字段总数。准备好了吗?
expr="[^0-9]${day}[^0-9]|^${day}[^0-9]|[^0-9]${day}\$"
cal aug 2008 | awk "/$expr/ { for (i=1;i<=NF;i++) {
if (\$i~/${day}/) { print \"i=\"i\", NF=\"NF }}}"
双引号增加了一点复杂性,但实际上,这只是一个复杂的脚本。
针对我们的 2008 年 8 月日历的输出看起来像这样
$ sh match.sh 2
i=2, NF=2
$ sh match.sh 10
i=1, NF=7
$ sh match.sh 19
i=3, NF=7
这一切都很有道理。接下来的挑战是弄清楚对于给定的日期和一周的天数,我们匹配的是星期几。请记住,三天周的第 1 天是星期四,而七天周的第 1 天是星期日。令人困惑,对吧?
星期几作为数组计算这个的快速方法是,嗯,通过创建一堆数组来预先计算它。像这样
if NF=1 days=[Sat]
if NF=2 days=[Fri,Sat]
if NF=3 days=[Thu,Fri,Sat]
等等。这里有一个公式在起作用,但更重要的是,有一个模式:(7-NF)-i 是一致的。因此,三天周的第 1 天是 (7-3)+1 = 5 = 星期四,而 7 天周的第 1 天是 (7-7)+1 = 星期日。
让我们再次检查:在 2008 年 8 月,8 月 1 日是 (7-2)+1 = 6 = 星期六,8 月 4 日 = (7-7)+2 = 星期一,8 月 31 日 = (7-1)+1 = 7 = 星期六。
糟糕,最后一个是错误的,表明我们需要区分本月的第一个星期,在这种情况下,日子是右对齐的(就像那样!),但在本月的最后一个星期,它们是左对齐的。
啊,另一个细微差别。天哪,这真是一个很难编写的脚本,不是吗?
下次,我们将继续构建脚本。同时,尝试 `awk` 和正则表达式,看看您是否能找到更精简的解决方案。
键盘照片来自 Shutterstock.com。