如何使用脚本将日期范围分割为天

如何使用脚本将日期范围分割为天

我有这个输入:

      startdate             end date         val1    val2
2015-10-13 07:00:02 2015-10-19 00:00:00      45      1900

其中一行指定跨越多天的日期范围,我想将该范围拆分为单独的时间段,每个时间段都是一天的子集(每个时间段在单独的行上),以方便并行处理(多- 天)范围。

输出应该是

2015-10-13 07:00:02 2015-10-13 23:59:59      45      1900
2015-10-14 00:00:01 2015-10-14 23:59:59      45      1900
2015-10-15 00:00:01 2015-10-15 23:59:59      45      1900
2015-10-16 00:00:01 2015-10-16 23:59:59      45      1900
2015-10-17 00:00:01 2015-10-17 23:59:59      45      1900
2015-10-18 00:00:01 2015-10-18 23:59:59      45      1900
2015-10-19 00:00:01 2015-10-19 00:00:00      45      1900

其中结束时间之后的数据(val1 和 val2)在每行上复制。 

  1. 实际上输入记录来自 hive 表,输出记录也将其存储在 split 表中。

修改:

日期分割很好。还需要根据分割日期分割 val2 值。

如果日期差异为 2 那么我们将分割 2 行,这应该是

  • 第 1 行:

比率=第一天花费的时间比率(即第一天的结束-开始)/值1

val2=比率*val2

  • 第 2 行:

比率=第一天花费的时间比率(即第二天的结束-开始)/值1

值2= 比率*val2

我该如何编写这个脚本?

答案1

该脚本将执行您想要的操作(如果我正确理解您的要求)。我冒昧地推断了您的规范,以允许输入具有一个标题行,然后具有任意数量的带有日期/时间范围的行。我将在下面对此进行说明并进一步讨论。

#!/bin/sh
if IFS= read header
then
        printf "%s\n" "$header"
else
        echo 'EOF on first line!' >&2
        exit 1
fi
while read start_date start_time end_date end_time other_data           # See note, below.
do
        start_epoch=$(date +"%s" -d "$start_date $start_time")  ||  {
                echo "Error processing start date&time $start_date $start_time" >&2
                exit 1
        }
        end_epoch=$(date +"%s" -d "$end_date $end_time")  ||  {
                echo "Error processing end date&time $end_date $end_time" >&2
                exit 1
        }
        if [ "$end_epoch" -lt "$start_epoch" ]
        then
                echo "End date&time $end_date $end_time is before start date&time $start_date $start_time" >&2
                # Now what?
                continue
        fi
        ok_seq=1        # Flag: we are moving forward.
        current_date="$start_date"
        current_time="$start_time"
        while [ "$ok_seq" -ne 0 ]
        do
                # Most days end at 23:59:59.
                eod_time="23:59:59"
                eod_epoch=$(date +"%s" -d "$current_date $eod_time")  ||  {
                        # This should never happen.
                        echo "Error processing end-of-day date&time $current_date $eod_time" >&2
                        exit 1
                }
                if [ "$end_epoch" -lt "$eod_epoch" ]    # We’re passing the end of the date/time range.
                then
                        if [ "$current_date" != "$end_date" ]
                        then
                                # Sanity check -- this should not happen.
                                echo "We're finishing, but the current date is $current_date and the end date is $end_date" >&2
                        fi
                        eod_time="$end_time"
                        ok_seq=0
                fi
                                                                        # See note, below.
                printf "%s %s %s %s      %s\n" "$current_date" "$current_time" "$current_date" "$eod_time" "$other_data"
                # We could also use +"%F" for the full YYYY-mm-dd date.
                current_date=$(date +"%Y-%m-%d" -d "$current_date next day")  ||  {
                        # This shouldn’t happen.
                        echo "Error getting next day after $current_date" >&2
                        exit 1
                }
                current_time="00:00:01"
        done
done

讨论:

  • 阅读标题行。如果失败,则中止脚本。如果成功,则将该行写入输出。如果(如您的问题所示)您不希望输出中包含标题,请删除该printf "%s\n" "$header"语句。
  • 如上所述:循环,从输入中读取开始/结束/值行,直到到达输入的末尾(或出现致命错误)。如果您不想这样做,请删除whiledo和相应的done
  • 读取开始日期、开始时间、结束日期、结束时间等数据。  other_data包括结束时间之后的所有内容,即 val1 和 val2(以及它们之间的所有空格)。
  • 使用该命令将任意日期/时间字符串转换为 Unix“纪元时间”——自 1970-01-01 00:00:00 (GMT) 以来的秒数。这让我们可以验证输入(并在出现错误时退出),并且还为我们提供了可以比较的数字。 (尽管我想我们可以对格式为 YYYY-MM-DD HH:MM:SS 的值进行字符串比较。)date +"%s" -d "date/time string"
  • 如果结束日期/时间早于开始日期/时间,则跳过此记录并转到下一行。如果您希望在这种情况下执行其他操作(例如终止),请更改此代码。
  • 设置一个标志 ( ok_seq),我们将用它来控制逐日循环。将第一天的开始日期/时间初始化为整个期间的开始日期/时间。
  • 在每个输出行上,开始日期和结束日期相同。在大多数线路上,一天结束 (eod) 时间为 23:59:59。如果(同一日期)+ 23:59:59 大于(晚于)期末日期/时间,则我们处于该范围的最后一天(输出行)。将 eod 时间设置为结束时间,并设置ok_seq为 0,以便我们退出循环。
  • 写入一行输出,包括“其他数据”(val1 和 val2 等)
  • 计算第二天的日期。将开始时间设置为 00:00:01,该时间将出现在除第一行之外的每个输出行上。

例子:

$ cat input
      startdate             end date         val1    val2
2015-10-13 07:00:02 2015-10-19 00:00:00      45      1900
2015-11-01 08:30:00 2015-11-05 15:00:00      42      6083
2015-12-27 12:00:00 2016-01-04 12:34:56      17      quux

$ ./script < input
      startdate             end date         val1    val2
2015-10-13 07:00:02 2015-10-13 23:59:59      45      1900
2015-10-14 00:00:01 2015-10-14 23:59:59      45      1900
2015-10-15 00:00:01 2015-10-15 23:59:59      45      1900
2015-10-16 00:00:01 2015-10-16 23:59:59      45      1900
2015-10-17 00:00:01 2015-10-17 23:59:59      45      1900
2015-10-18 00:00:01 2015-10-18 23:59:59      45      1900
2015-10-19 00:00:01 2015-10-19 00:00:00      45      1900
2015-11-01 08:30:00 2015-11-01 23:59:59      42      6083
2015-11-02 00:00:01 2015-11-02 23:59:59      42      6083
2015-11-03 00:00:01 2015-11-03 23:59:59      42      6083
2015-11-04 00:00:01 2015-11-04 23:59:59      42      6083
2015-11-05 00:00:01 2015-11-05 15:00:00      42      6083
2015-12-27 12:00:00 2015-12-27 23:59:59      17      quux
2015-12-28 00:00:01 2015-12-28 23:59:59      17      quux
2015-12-29 00:00:01 2015-12-29 23:59:59      17      quux
2015-12-30 00:00:01 2015-12-30 23:59:59      17      quux
2015-12-31 00:00:01 2015-12-31 23:59:59      17      quux
2016-01-01 00:00:01 2016-01-01 23:59:59      17      quux
2016-01-02 00:00:01 2016-01-02 23:59:59      17      quux
2016-01-03 00:00:01 2016-01-03 23:59:59      17      quux
2016-01-04 00:00:01 2016-01-04 12:34:56      17      quux

观察它没有问题,不仅从一个月滚动到下一个月,而且从一年滚动到下一年。


笔记: 当我编写上述版本的脚本时,我不知道如何捕获结束时间和 val1 之间的空白,所以我得到的输出看起来像

      startdate             end date         val1    val2
2015-10-13 07:00:02 2015-10-13 23:59:59 45      1900
2015-10-14 00:00:01 2015-10-14 23:59:59 45      1900
2015-10-15 00:00:01 2015-10-15 23:59:59 45      1900

所以我“作弊”了,在命令中添加了“适量”的空间printf(在最后一个之前%s)。但是,如果您更改输入中的间距,上述版本的脚本将再次生成不正确对齐的列。我想出了如何解决它,尽管有点混乱。将while … do…行替换start_epoch=…为:

while read start_date start_time end_date other_data
do
        # $other_data includes end_time and all the following values.
        # Break them apart:
        end_time="${other_data%%[       ]*}"
        other_data="${other_data#"$end_time"}"
        start_epoch=…

whereend_time已从read命令中删除,括号[和 the之间的字符] 是空格和制表符。所以现在other_data包含 val1 之前的空格。然后将其更改printf

                printf "%s %s %s %s%s\n" "$current_date" "$current_time" "$current_date" "$eod_time" "$other_data"

(请注意,有第四个和第五个之间的空间%s)。现在你已经完成了。

答案2

我猜您正在寻求摆脱顶部标题行。假设您从中获取输入的函数称为“timefunc”。您可能想尝试在 cut 命令中管道 timefunc 的输出,如下所示:

timefunc | cut -d$'\n' -f2

现在的输出是:

2015-10-13 07:00:02 2015-10-19 00:00:00      45      1900

答案3

您可以使用 grep 从输出中去除标题行:

inputcmd | grep -v startdate

相关内容