How to loop through dates using Bash?

I have such bash script:

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


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

Now I want to loop through a range of dates, e.g. 2015-01-01 until 2015-01-31.

How to achieve in Bash?

Update:

Nice-to-have: No job should be started before a previous run has completed. In this case, when executeJobs.py is completed bash prompt $ will return.

e.g. could I incorporate wait%1 in my loop?

124535 次浏览

括号扩展 :

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} …

Proof:

$ 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

Compact/nested:

$ 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 -n -t"-" -k1 -k2 -k3) )
$ 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

使用 GNU 日期:

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


# mac option for d decl (the +1d is equivalent to + 1 day)
# d=$(date -j -v +1d -f "%Y-%m-%d" $d +%Y-%m-%d)
done

注意,因为这使用字符串比较,所以需要完整的 ISO8601边缘日期符号(不要删除前导零)。要检查有效的输入数据,如果可能的话,还可以使用 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

最后一个补充 : 为了检查 $startdate是否在 $enddate之前,如果您只希望日期在1000年和9999年之间,您可以像这样简单地使用字符串比较:

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,其思想是这种形式的日期可以进行数字比较。

如果一个人想循环从输入日期到任何范围以下可以使用,也将打印输出的格式 yyyMMdd..。

#!/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

我有同样的问题,我尝试了上面的一些答案,也许他们是好的,但没有一个答案固定在我正在尝试做什么,使用 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

希望能有帮助。

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 $(date -d $start +%Y-%m-%d)
start=$(date -d"$start + 1 day" +"%Y%m%d")


done

我需要遍历 AIX、 bsd、 Linux、 osx 和 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

And the C code:

#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;
}

Bash is best written by leveraging pipes(|). This should result in memory efficient and concurrent(faster) processing. I would write the following:

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

下面将打印 20 aug 2020的日期和之前100天的日期。

这个一线程可以做成一个实用程序。

#!/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

假设我们希望生成某个特定日期的日期。我们还不知道到达那里需要多少次迭代。假设汤姆生于2001年1月1日。我们希望生成每个日期,直到确定一个日期。我们可以通过使用 sed 来实现这一点。

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

$((2**63-1))技巧用于创建一个大整数。

一旦 sed 退出,它也将退出 date-range 实用程序。

还可以使用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

如果你被 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秒。

之前@Gilli 的解决方案非常聪明,因为它利用了这样一个事实,即可以简单地将两个日期格式化,使它们看起来像整数。然后可以使用-le/less-equals-通常只对数值数据起作用。

问题是,这会将您绑定到日期格式 YMD,比如20210201。如果您需要一些不同的东西,比如2021-02-01(这就是 OP 所暗示的需求) ,那么这个脚本将无法工作:

start='2021-02-01'
end='2021-02-05'


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

输出如下:

2021-02-01
2021-02-02
2021-02-03
2021-02-04
2021-02-05
2021-02-06
2021-02-07
./loop.sh: line 16: [[: 2021-02-08: value too great for base (error token is "08")

为了解决这个问题,并使用这个循环定制日期格式,您需要使用一个额外的变量,让我们称之为“ 开始”:

d_start='2021-02-01'
end='2021-02-05'


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


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


done

这将产生以下结果:

2021-02-01
2021-02-02
2021-02-03
2021-02-04
2021-02-05

这可能也有帮助。基于 吉利的答案,但是一个不同的整数转换问题的解决方案。

Basically, while verifying the input, LoopEachDay stores the "end" date in 几秒钟 and compares with it firstly converting the current day into seconds(date -d "$dateIteration" '+%s'), too.

#/bin/bash


RegexVerify()
{
regex="$1";
shift;


if [[ "$@" =~ $regex ]];
then
return 0;
fi


return 1;
}


VerifyDateISO8601()
{
if RegexVerify '^[0-9]{4}-(0?[1-9]|10|11|12)-(0?[1-9]|[12][0-9]|3[01])$' "$1";
then
return 0;
fi


return 1;
}


# Iterate each day
#
# * The *first* argument is an ISO8601 start date.
# * The *second* argument is an ISO8601 end date or an empty string which assumes
# the current date.
LoopEachDay()
{
if ! VerifyDateISO8601 "$1";
then
return 1;
fi


if ! VerifyDateISO8601 "$2" && [ "$2" != '' ];
then
return 2;
fi


dateIteration="$(date -d "$1" '+%Y-%m-%d')";
dateIterationEndSeconds="$(date -d "$2" '+%s')";


while (("$(date -d "$dateIteration" '+%s')" <= dateIterationEndSeconds))
do
printf $'%s\n' "$dateIteration"; # A work with "$dateIteration"


dateIteration="$(date -d "$dateIteration + 1 day" '+%Y-%m-%d')";
done
}


LoopEachDay '2021-13-01' '';
printf $'Exit code: %s\n\n' "$?";


# Exit code: 1


LoopEachDay '2021-04-01' '';


# 2021-04-01
# 2021-04-02
# 2021-04-03
# 2021-04-04
# 2021-04-05
# 2021-04-06
# 2021-04-07
# 2021-04-08


printf $'\n';
LoopEachDay '2021-04-03' '2021-04-06';


# 2021-04-03
# 2021-04-04
# 2021-04-05
# 2021-04-06