
我有這個意見:
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