スクリプトを使用して日付範囲を日単位で分割する方法

スクリプトを使用して日付範囲を日単位で分割する方法

次の入力があります。

      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)が各行にコピーされます。 

  1. 実際には、入力レコードはハイブテーブルから出力され、出力レコードもそれをパーティションテーブルに格納します。

改訂する:

日付分割は大丈夫です。また、分割日に基づいて値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_dataprintf

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

(参考にしてください。いいえ4番目と5番目の間のスペース%s)。これで終わりました。

おすすめ記事