Bash를 사용하여 날짜를 반복하는 방법은 무엇입니까?


85

그런 bash 스크립트가 있습니다.

array=( '2015-01-01', '2015-01-02' )

for i in "${array[@]}"
do
    python /home/user/executeJobs.py {i} &> /home/user/${i}.log
done

이제 날짜 범위 (예 : 2015-01-01부터 2015-01-31까지)를 반복하고 싶습니다.

Bash에서 달성하는 방법?

업데이트 :

가지고 있으면 좋다 : 이전 실행이 완료되기 전에 작업을 시작해서는 안됩니다. 이 경우 executeJobs.py가 완료되면 bash 프롬프트 $가 반환됩니다.

예를 들어 wait%1내 루프에 통합 할 수 있습니까?


GNU 날짜를 사용하는 플랫폼에 있습니까?
Charles Duffy

1
이 링크를 확인하십시오 : glatter-gotz.com/blog/2011/02/19/…
qqibrow

1
BTW, Python 인터프리터가 있으므로 datetimePython 모듈을 사용하여 안정적이고 이식 가능한 방식으로 훨씬 쉽게 수행 할 수 있습니다.
Charles Duffy

3
2015-01-01 ~ 2015-01-31은 한 달이 넘는 날짜에 걸쳐 있지 않으므로 매우 간단한 경우입니다.
Wintermute 2015 년

2
... 따라서 실제로 필요한 경우 wait(예 : 동시 프로세스로 인해 발생하는 버그), 더 흥미 롭거나 더 복잡한 문제가 발생하여 더 복잡한 솔루션이 필요합니다 ( 하위 프로세스가 잠금 파일을 상속하도록 요청하는 것과 같이), 이는 충분히 복잡하고 날짜 산술과 관련이 없으므로 별도의 질문이어야합니다.
Charles Duffy

답변:


197

GNU 날짜 사용 :

d=2015-01-01
while [ "$d" != 2015-02-20 ]; do 
  echo $d
  d=$(date -I -d "$d + 1 day")
done

이것은 문자열 비교를 사용하기 때문에 엣지 날짜의 완전한 ISO 8601 표기법이 필요합니다 (선행 0을 제거하지 마십시오). 유효한 입력 데이터를 확인하고 가능한 경우 유효한 형식으로 강제 변환하려면 다음을 사용할 수도 있습니다 date.

# slightly malformed input data
input_start=2015-1-1
input_end=2015-2-23

# After this, startdate and enddate will be valid ISO 8601 dates,
# or the script will have aborted when it encountered unparseable data
# such as input_end=abcd
startdate=$(date -I -d "$input_start") || exit -1
enddate=$(date -I -d "$input_end")     || exit -1

d="$startdate"
while [ "$d" != "$enddate" ]; do 
  echo $d
  d=$(date -I -d "$d + 1 day")
done

마지막 추가 사항 : 1000 년과 9999 년 사이의 날짜 만 예상하는 경우 $startdate이전 $enddate인지 확인하려면 다음과 같이 문자열 비교를 사용하면됩니다.

while [[ "$d" < "$enddate" ]]; do

사전 식 비교가 실패 할 때 10000 년 이후 매우 안전한 편이 되려면 다음을 사용하십시오.

while [ "$(date -d "$d" +%Y%m%d)" -lt "$(date -d "$enddate" +%Y%m%d)" ]; do

이 표현 은 숫자 형식으로 $(date -d "$d" +%Y%m%d)변환 $d됩니다. 즉, 2015-02-23가됩니다 20150223. 아이디어는이 형식의 날짜를 숫자로 비교할 수 있다는 것입니다.


1
물론입니다. 반복자가 내부에서 수행 할 수있는 작업을 변경하지 않으므로 날짜를 사용하는 쉘 루프 일뿐입니다.
Wintermute 2015 년

1
@SirBenBenji, ... 즉, %1작업 제어 구조이며 명시 적으로 직접 설정하지 않는 한 비대화 형 스크립트에서 작업 제어가 해제됩니다. 스크립트 내에서 각각의 서브 프로세스를 참조하는 적절한 방법은 PID입니다, 심지어 다음, 완료하는 프로세스를 기다리는 것은 자동 명시 적으로 (A에서와 같이 코드에 의해 백그라운드로하지 않는 한 &(), 또는 자기 분리하는 경우 wait심지어 작업하지 않습니다 쉘에게 주어진 PID는 자기 배경에 사용되는 이중 포크 과정)에 의해 무효화 될 것이다.
Charles Duffy

1
자세히 살펴보면 윤초가 UNIX 타임 스탬프에서 제외되어 일부 타임 스탬프가 2 초의 공간을 참조하는 것으로 보입니다. 이것은 "gettimeofday"를 1 초 미만의 범위에서 구현하는 것이 매우 흥미로워 보이게합니다. 그리고 윤초 가 1 년에서 제거 되지 않았다는 사실을 행운으로 생각해야한다고 생각합니다 . 이것은 나 자신을 수정해야 함을 의미합니다. 유닉스 타임 스탬프에 86400 초를 추가하는 것은 2016-12-31T23 : 59 : 60을 구체적으로 참조 할 방법이 없기 때문에 항상 하루를 추가하는 것과 동일합니다. TIL.
Wintermute 2017

2
첫 번째 코드 (sh test.sh)를 실행하면 오류가 발생합니다. date : illegal option-I 사용법 : date [-jnRu] [-d dst] [-r seconds] [-t west] [-v [+ | -] val [ymwdHMS]] ... [-f fmt 날짜 | [[[mm] dd] HH] MM [[cc] yy] [. ss]] [+ format]
dorien

2
macOS의 경우 작동하지 않습니다. 먼저 gnu date apple.stackexchange.com/questions/231224/를
Jaime Agudo

22

중괄호 확장 :

for i in 2015-01-{01..31} …

더:

for i in 2015-02-{01..28} 2015-{04,06,09,11}-{01..30} 2015-{01,03,05,07,08,10,12}-{01..31} …

증명:

$ echo 2015-02-{01..28} 2015-{04,06,09,11}-{01..30} 2015-{01,03,05,07,08,10,12}-{01..31} | wc -w
 365

압축 / 중첩 :

$ echo 2015-{02-{01..28},{04,06,09,11}-{01..30},{01,03,05,07,08,10,12}-{01..31}} | wc -w
 365

중요한 경우 주문 :

$ x=( $(printf '%s\n' 2015-{02-{01..28},{04,06,09,11}-{01..30},{01,03,05,07,08,10,12}-{01..31}} | sort) )
$ echo "${#x[@]}"
365

순서가 지정되지 않았기 때문에 다음과 같이 윤년을 맞출 수 있습니다.

$ echo {2015..2030}-{02-{01..28},{04,06,09,11}-{01..30},{01,03,05,07,08,10,12}-{01..31}} {2016..2028..4}-02-29 | wc -w
5844

3
윤년은 어떻습니까?
Wintermute 2015 년

그런 다음 사용할 수 있습니까 python /home/user/executeJobs.py 2015-01-{01..31} &> /home/user/2015-01-{01..31}.log ?
Steve K

@SirBenBenji에 따라 다릅니다 executeJobs.py.
kojiro

executeJobs에 날짜 매개 변수가 필요하며 각 실행이 완료 될 때까지 기다려야한다고 말해야합니다. 이는 빅 데이터 작업이며 각 이전 실행이 완료되기 전에 어떤 상황에서도 시작되어서는 안됩니다. 나는 이것을 전에 생각 했어야했다. 잊어 버려서 미안하다.
Steve K

10
start='2019-01-01'
end='2019-02-01'

start=$(date -d $start +%Y%m%d)
end=$(date -d $end +%Y%m%d)

while [[ $start -le $end ]]
do
        echo $start
        start=$(date -d"$start + 1 day" +"%Y%m%d")

done

3

나는 같은 문제가 있었고 위의 답변 중 일부를 시도했지만 괜찮을 수도 있지만 macOS를 사용하여 내가하려는 작업에 대한 답변이 수정되지 않았습니다.

나는 과거의 날짜를 반복하려고 시도했으며 다음은 나를 위해 일한 것입니다.

#!/bin/bash

# Get the machine date
newDate=$(date '+%m-%d-%y')

# Set a counter variable
counter=1 

# Increase the counter to get back in time
while [ "$newDate" != 06-01-18 ]; do
  echo $newDate
  newDate=$(date -v -${counter}d '+%m-%d-%y')
  counter=$((counter + 1))
done

도움이되기를 바랍니다.


매우 강력한 비트 단위 복사 명령과 일치하는 변수 이름을 사용하지 않는 것이 좋습니다.
MerrillFraz

더 간단한 방법은 macOS gdate대신 사용하는 것입니다 date.
northtree

2

입력 날짜에서 아래 범위로 반복하려면 yyyyMMdd 형식으로 출력을 인쇄합니다.

#!/bin/bash
in=2018-01-15
while [ "$in" != 2018-01-25 ]; do
  in=$(date -I -d "$in + 1 day")
  x=$(date -d "$in" +%Y%m%d)
  echo $x
done

2

AIX, BSD, Linux, OS X 및 Solaris에서 날짜를 반복해야했습니다. 이 date명령은 내가 만난 플랫폼에서 사용하기에 가장 덜 이식 가능하고 가장 비참한 명령 중 하나입니다. 나는 쓰기가 더 쉽다는 것을 알았다my_date어디서나 작동 명령 .

아래의 C 프로그램은 시작일을 취하고 여기에서 일을 더하거나 뺍니다. 날짜가 제공되지 않으면 현재 날짜에서 일을 더하거나 뺍니다.

my_date명령을 사용하면 어디에서나 다음을 수행 할 수 있습니다.

start="2015-01-01"
stop="2015-01-31"

echo "Iterating dates from ${start} to ${stop}."

while [[ "${start}" != "${stop}" ]]
do
    python /home/user/executeJobs.py {i} &> "/home/user/${start}.log"
    start=$(my_date -s "${start}" -n +1)
done

그리고 C 코드 :

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>

int show_help();

int main(int argc, char* argv[])
{
    int eol = 0, help = 0, n_days = 0;
    int ret = EXIT_FAILURE;

    time_t startDate = time(NULL);
    const time_t ONE_DAY = 24 * 60 * 60;

    for (int i=0; i<argc; i++)
    {
        if (strcmp(argv[i], "-l") == 0)
        {
            eol = 1;
        }
        else if (strcmp(argv[i], "-n") == 0)
        {
            if (++i == argc)
            {
                show_help();
                ret = EXIT_FAILURE;
                goto finish;
            }

            n_days = strtoll(argv[i], NULL, 0);
        }
        else if (strcmp(argv[i], "-s") == 0)
        {
            if (++i == argc)
            {
                show_help();
                ret = EXIT_FAILURE;
                goto finish;
            }

            struct tm dateTime;
            memset (&dateTime, 0x00, sizeof(dateTime));

            const char* start = argv[i];
            const char* end = strptime (start, "%Y-%m-%d", &dateTime);

            /* Ensure all characters are consumed */
            if (end - start != 10)
            {
                show_help();
                ret = EXIT_FAILURE;
                goto finish;
            }

            startDate = mktime (&dateTime);
        }
    }

    if (help == 1)
    {
        show_help();
        ret = EXIT_SUCCESS;
        goto finish;
    }

    char buff[32];
    const time_t next = startDate + ONE_DAY * n_days;
    strftime(buff, sizeof(buff), "%Y-%m-%d", localtime(&next));

    /* Paydirt */
    if (eol)
        fprintf(stdout, "%s\n", buff);
    else
        fprintf(stdout, "%s", buff);

    ret = EXIT_SUCCESS;

finish:

    return ret;
}

int show_help()
{
    fprintf(stderr, "Usage:\n");
    fprintf(stderr, "  my_date [-s date] [-n [+|-]days] [-l]\n");
    fprintf(stderr, "    -s date: optional, starting date in YYYY-MM-DD format\n");
    fprintf(stderr, "    -n days: optional, number of days to add or subtract\n");
    fprintf(stderr, "    -l: optional, add new-line to output\n");
    fprintf(stderr, "\n");
    fprintf(stderr, "  If no options are supplied, then today is printed.\n");
    fprintf(stderr, "\n");
    return 0;
}

2

Bash는 파이프 (|)를 활용하여 작성하는 것이 가장 좋습니다. 이것은 메모리 효율적이고 동시 (더 빠른) 처리를 가져야합니다. 다음과 같이 작성합니다.

seq 0 100 | xargs printf "20 Aug 2020 - %sdays\n" \
  | xargs -d '\n' -l date -d

다음은 날짜를 20 aug 2020인쇄하고 그 전 100 일의 날짜를 인쇄합니다.

이 oneliner는 유틸리티로 만들 수 있습니다.

#!/usr/bin/env bash

# date-range template <template>

template="${1:--%sdays}"

export LANG;

xargs printf "$template\n" | xargs -d '\n' -l date -d

기본적으로 한 번에 지난 1 일로 반복하도록 선택합니다.

$ seq 10 | date-range
Mon Mar  2 17:42:43 CET 2020
Sun Mar  1 17:42:43 CET 2020
Sat Feb 29 17:42:43 CET 2020
Fri Feb 28 17:42:43 CET 2020
Thu Feb 27 17:42:43 CET 2020
Wed Feb 26 17:42:43 CET 2020
Tue Feb 25 17:42:43 CET 2020
Mon Feb 24 17:42:43 CET 2020
Sun Feb 23 17:42:43 CET 2020
Sat Feb 22 17:42:43 CET 2020

특정 날짜까지의 날짜를 생성한다고 가정 해 보겠습니다. 우리는 거기에 도달하기 위해 얼마나 많은 반복이 필요한지 아직 모릅니다. Tom이 2001 년 1 월 1 일에 태어 났다고 가정 해 보겠습니다. 특정 날짜까지 각 날짜를 생성하려고합니다. sed를 사용하여이를 달성 할 수 있습니다.

seq 0 $((2**63-1)) | date-range | sed '/.. Jan 2001 /q'

$((2**63-1))트릭은 큰 정수를 생성하는 데 사용됩니다.

sed가 종료되면 날짜 범위 유틸리티도 종료됩니다.

3 개월 간격을 사용하여 반복 할 수도 있습니다.

$ seq 0 3 12 | date-range '+%smonths'
Tue Mar  3 18:17:17 CET 2020
Wed Jun  3 19:17:17 CEST 2020
Thu Sep  3 19:17:17 CEST 2020
Thu Dec  3 18:17:17 CET 2020
Wed Mar  3 18:17:17 CET 2021

이 아이디어를 개선하고 좀 더 잘 문서화하는 date-seq 저장소를 만들었습니다. github.com/bas080/date-seq
bas080

1

busybox 날짜에 갇혀 있다면 타임 스탬프 작업이 가장 신뢰할 수있는 방법이라는 것을 알았습니다.

STARTDATE="2019-12-30"
ENDDATE="2020-01-04"

start=$(date -d $STARTDATE +%s)
end=$(date -d $ENDDATE +%s)

d="$start"
while [[ $d -le $end ]]
do
    date -d @$d +%Y-%m-%d

    d=$(( $d + 86400 ))
done

그러면 다음이 출력됩니다.

2019-12-30
2019-12-31
2020-01-01
2020-01-02
2020-01-03
2020-01-04

Unix 타임 스탬프에는 윤초가 포함되지 않으므로 1 일은 항상 정확히 86400 초와 같습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.