
次のような入力があります:
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) が各行に複製されます。
- 実際には、入力レコードは Hive テーブルから取得され、出力レコードも分割テーブルに格納されます。
変更点:
日付の分割は問題ありません。分割日付に従って val2 値も分割する必要があります。
日付の差が2の場合、2行に分割します。
- 1行目:
比率= 1日目に費やした時間の比率(つまり、1日目の終了-開始)/値1
val2 = 比率*val2
- 2行目:
比率= 1日目に費やした時間の比率(つまり、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 の「エポック タイム」、つまり 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=…
ここで、はコマンドend_time
から削除されread
、括弧[
と の間の文字は]
スペースとタブです。つまり、other_data
val1 の前のスペースが含まれるようになりました。次にprintf
を に変更します。
printf "%s %s %s %s%s\n" "$current_date" "$current_time" "$current_date" "$eod_time" "$other_data"
(注意:いいえ4番目と5番目の間にスペースを入れます%s
。これで完了です。
答え2
おそらく、先頭のヘッダー行を削除しようとしているのだと思います。この入力を取得している関数の名前が「timefunc」だとします。次のように、timefunc の出力を cut コマンドでパイプしてみるとよいでしょう。
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