我有这个输入:
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)在每行上复制。
- 实际上输入记录来自 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"
语句。 - 如上所述:循环,从输入中读取开始/结束/值行,直到到达输入的末尾(或出现致命错误)。如果您不想这样做,请删除
while
、do
和相应的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