如何使用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年1月1日至2015年1月31日。

如何在Bash中实现?

更新

很好:在上一次运行完成之前,不应开始任何作业。在这种情况下,executeJobs.py完成后,bash提示$将返回。

例如,我可以加入wait%1我的循环吗?


您是否在使用GNU date的平台上?
查尔斯·达菲


1
顺便说一句,由于您拥有方便的Python解释器,因此使用datetimePython模块以可靠且可移植的方式进行此操作要容易得多。
查尔斯·达菲

3
2015年1月1日至2015年1月31日之间的日期跨度不超过一个月,因此这是一个非常简单的案例。
Wintermute 2015年

2
...因此,如果您实际上是有需要wait(例如,由于并发进程而导致的bug发生),那么您会遇到一些更有趣/更复杂的事情,这需要更复杂的解决方案(例如要求子流程继承锁定文件),这足够复杂并且与日期算术无关,因此它应该是一个单独的问题。
查尔斯·达菲

Answers:


197

使用GNU日期:

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

请注意,由于这使用字符串比较,因此需要边缘日期的完整ISO 8601表示法(请勿删除前导零)。要检查有效的输入数据并在可能的情况下将其强制转换为有效的格式,还可以使用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,并且想法是可以对该形式的日期进行数字比较。


1
当然可以,为什么不呢。这只是一个shell循环,它使用日期,因为迭代器不会更改您可以在其中执行的操作。
Wintermute 2015年

1
@SirBenBenji,...说,这%1是一个作业控制结构,除非您自己明确将其打开,否则在非交互式脚本中将关闭作业控制。在脚本中引用单个子流程的正确方法是使用PID,即使这样,除非您的代码将它们明确地作为背景(如使用)或它们自行分离(在这种情况下),等待流程自动完成也是自动的。甚至无法正常工作,并且用于外壳的PID将被用于自背景的双叉过程无效)。&wait
查尔斯·达菲

1
在仔细查看之后,leap秒似乎已从UNIX时间戳中排除,因此某些时间戳指的是两秒的间隔。这显然使“ gettimeofday”在不到一秒的范围内实现非常有趣,并且我想我们应该为自己感到幸运,因为leap秒从未从一年中移除。这意味着我必须纠正自己:可以将Unix时间戳添加86400秒与添加一天总是一样的,因为无法具体引用2016-12-31T23:59:60。瓷砖。
Wintermute

2
运行您的第一个代码(sh test.sh)给我一个错误:日期:非法选项-我的用法:日期[-jnRu] [-d dst] [-r秒] [-t西] [-v [+ | -] val [ymwdHMS]] ... [-f fmt日期| [[[mm] dd] HH] MM [[cc] yy] [
。ss

2
适用于MacOS也不会工作,首先安装GNU日期apple.stackexchange.com/questions/231224/...
海梅·阿古

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
leap年呢?
Wintermute 2015年

我可以使用python /home/user/executeJobs.py 2015-01-{01..31} &> /home/user/2015-01-{01..31}.log 吗?
Steve K

@SirBenBenji取决于executeJobs.py
kojiro 2015年

我必须说executeJobs需要date参数,并且我需要等待每次运行完成。这是一项大数据作业,在任何情况下都不应在之前的每次运行完成之前启动。我以前应该考虑过这一点,对不起忘记了。
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

更简单的方法是使用gdate而不是date在macOS中使用。
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天的日期。

该单件套可以制成实用程序。

#!/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退出后,它还将退出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

我制作了一个date-seq储存库,对该储存库进行了改进,并对其进行了更好的记录。github.com/bas080/date-seq
bas080

1

如果您不喜欢忙碌的日期,我发现使用时间戳是最可靠的方法:

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时间戳不包含leap秒,因此1天始终等于86400秒。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.