次の入力があります。
startdate end date val1 val2
2015-10-13 07:00:02 2015-10-19 00:00:00 45 1900
行の1つは、複数の日にわたる日付範囲を指定し、並列処理(複数日)の範囲を容易にするために、範囲を別の期間に分割したいと思います。各期間は、1日のサブセット(別々の行の各期間)です。
出力は
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)が各行にコピーされます。
- 実際には、入力レコードはハイブテーブルから出力され、出力レコードもそれをパーティションテーブルに格納します。
改訂する:
日付分割は大丈夫です。また、分割日に基づいて値val2を分割する必要があります。
日付差が2の場合、2つの行を分割します。
- ライン1:
比率=最初の日に費やした時間の割合(つまり、最初の日の終わり - 開始)/値1
値2=比率*値2
- 2号線:
比率=最初の日に費やした時間の割合(つまり、2日目の終わり~開始)/値1
値2=比率*値2
このスクリプトをどのように書くべきですか?
答え1
このスクリプトはあなたが望むことを行います(あなたの要件を正しく理解した場合)。入力にヘッダー行が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 "epoch time"(1970-01-01 00:00:00(GMT)以降の秒数)に変換できます。これにより、入力を検証し(エラーが発生したときに終了)、比較できる数値も提供されます。 (しかし、YYYY-MM-DD HH:MM:SS形式の値に対して文字列比較を実行できるとします。)
date +"%s" -d "date/time string"
- 終了日時が開始日時より前の場合は、このレコードをスキップして次の行に移動します。この場合、他の操作(終了など)を行うには、このコードを変更してください。
ok_seq
毎日のループを制御するために使用するフラグ()を設定します。最初の日の開始日時を全期間の開始日時に初期化します。- 各出力行では、開始日と終了日は同じです。ほとんどの行では、1日の終了時刻は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
1ヶ月から翌月に移動するだけでなく、1年から翌年に移動することも問題ありません。
メモ: 上記のバージョンのスクリプトを書いたときに、終了時間と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=…
コマンドから削除されend_time
た場所では、角かっことの間の文字はスペースとタブです。これで、val1の前のスペースが含まれます。次に、次のように変更します。read
[
]
other_data
printf
printf "%s %s %s %s%s\n" "$current_date" "$current_time" "$current_date" "$eod_time" "$other_data"
(参考にしてください。いいえ4番目と5番目の間のスペース%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