스크립트를 사용하여 날짜 범위를 일 단위로 분할하는 방법

스크립트를 사용하여 날짜 범위를 일 단위로 분할하는 방법

다음과 같은 입력이 있습니다.

      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)가 각 행에 복사됩니다. 

  1. 실제로 입력 레코드는 하이브 테이블에서 나오고 출력 레코드도 이를 분할 테이블에 저장합니다.

개정하다:

날짜 분할은 괜찮습니다. 또한 분할 날짜를 기준으로 val2 값을 분할해야 합니다.

날짜 차이가 2이면 2개의 행을 분할합니다.

  • 라인 1:

비율 = 첫날에 소비한 시간의 비율(즉, 첫날의 끝 - 시작)/값 1

값2=비율*값2

  • 2호선:

비율 = 첫날에 소비한 시간의 비율(즉, 둘째 날의 끝~시작)/값 1

값 2= 비율*값2

이 스크립트를 어떻게 작성해야 하나요?

답변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 시간"(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

한 달에서 다음 달로 이동하는 것뿐만 아니라 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"

(참고하세요.아니요네 번째와 다섯 번째 사이의 공간 %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

관련 정보