Bash:循环播放日期

mes*_*lds 66 bash

我有这样的bash脚本:

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

for i in "${array[@]}"
do
    python /home/user/executeJobs.py {i} &> /home/user/${i}.log
done
Run Code Online (Sandbox Code Playgroud)

现在我想循环一系列日期,例如2015-01-01至2015-01-31.

如何在Bash中实现?

更新:

很高兴:在上次运行完成之前不应该启动任何工作.在这种情况下,当executeJobs.py完成时,$将返回bash提示符.

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

Win*_*ute 156

使用GNU日期:

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

请注意,因为它使用字符串比较,所以它需要完整的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
Run Code Online (Sandbox Code Playgroud)

最后一个补充:要检查$startdate之前的情况$enddate,如果你只想要1000年和9999年之间的日期,你可以简单地使用这样的字符串比较:

while [[ "$d" < "$enddate" ]]; do
Run Code Online (Sandbox Code Playgroud)

为了在超过10000年的非常安全的一面,当词典比较崩溃时,使用

while [ "$(date -d "$d" +%Y%m%d)" -lt "$(date -d "$enddate" +%Y%m%d)" ]; do
Run Code Online (Sandbox Code Playgroud)

表达式$(date -d "$d" +%Y%m%d)转换$d为数字形式,即2015-02-23变为20150223,并且想法是可以在数字上比较这种形式的日期.

  • 对于 macOS 它不起作用,首先安装 gnu date https://apple.stackexchange.com/questions/231224/how-to-have-gnus-date-in-os-x (3认同)
  • @SirBenBenji,...也就是说,“%1”是一个作业控制构造,并且作业控制在非交互式脚本中被关闭,除非您自己明确地打开它。引用脚本内各个子进程的正确方法是通过 PID,即使如此,等待进程完成也是“自动”的,除非它们由您的代码显式后台支持(如使用“&amp;”),或者它们自行detach(在这种情况下,“wait”甚至不起作用,并且给 shell 的 PID 将被用于自我后台的双分叉进程失效)。 (2认同)
  • 仔细观察后发现,UNIX 时间戳中似乎排除了闰秒,因此某些时间戳指的是两秒的间隔。这显然使得“gettimeofday”在亚秒范围内实现非常有趣,我想我们应该庆幸闰秒从未从一年中“删除”。这意味着我必须纠正自己:向 unix 时间戳添加 86400 秒可以说总是与添加一天相同,因为无法具体引用 2016-12-31T23:59:60。直到。 (2认同)
  • 运行你的第一个代码(sh test.sh)给我一个错误:日期:非法选项——我用法:日期[-jnRu] [-d dst] [-r seconds] [-t west] [-v[+| -]val[ymwdHMS]] ... [-f fmt 日期 | [[[mm]dd]HH]MM[[cc]yy][.ss]] [+格式] (2认同)

koj*_*iro 19

支撑扩展:

for i in 2015-01-{01..31} …
Run Code Online (Sandbox Code Playgroud)

更多:

for i in 2015-02-{01..28} 2015-{04,06,09,11}-{01..30} 2015-{01,03,05,07,08,10,12}-{01..31} …
Run Code Online (Sandbox Code Playgroud)

证明:

$ 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
Run Code Online (Sandbox Code Playgroud)

紧凑型/嵌套:

$ echo 2015-{02-{01..28},{04,06,09,11}-{01..30},{01,03,05,07,08,10,12}-{01..31}} | wc -w
 365
Run Code Online (Sandbox Code Playgroud)

订购,如果重要:

$ 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
Run Code Online (Sandbox Code Playgroud)

由于它是无序的,你只需要闰年:

$ 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
Run Code Online (Sandbox Code Playgroud)

  • leap年呢? (3认同)
  • @SirBenBenji 这取决于 `executeJobs.py`。 (2认同)

小智 7

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
Run Code Online (Sandbox Code Playgroud)

  • 小心,这个是基于 bash 不检查变量类型这一事实。它仅适用于日期格式 YMD - 因为它将日期连接成看起来像整数的东西,这使得两个日期具有可比性。虽然 20200201 将被视为小于 20200202,但它不适用于 2020-02-01 和 2020-02-02。OP 要求用破折号分隔日期。 (3认同)

n.r*_*.r. 7

@Gilli 之前的解决方案非常聪明,因为它利用了这样一个事实:您可以简单地格式化两个日期,使它们看起来像整数。然后您可以使用 -le / less-equal - 它通常仅适用于数字数据。

问题是,这会将您绑定到日期格式 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
Run Code Online (Sandbox Code Playgroud)

输出将如下所示:

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")
Run Code Online (Sandbox Code Playgroud)

要解决此问题并将此循环用于自定义日期格式,您需要使用一个附加变量,我们将其称为“ d_start ”:

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
Run Code Online (Sandbox Code Playgroud)

这将导致以下输出:

2021-02-01
2021-02-02
2021-02-03
2021-02-04
2021-02-05
Run Code Online (Sandbox Code Playgroud)


jww*_*jww 6

我需要循环访问 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
Run Code Online (Sandbox Code Playgroud)

以及 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;
}
Run Code Online (Sandbox Code Playgroud)


Gel*_*ler 6

如果您受困于许多发行版(例如 Docker 容器中常用的 alpine)中使用的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
Run Code Online (Sandbox Code Playgroud)

这将输出:

2019-12-30
2019-12-31
2020-01-01
2020-01-02
2020-01-03
2020-01-04
Run Code Online (Sandbox Code Playgroud)

Unix 时间戳不包括闰秒,因此 1 天始终等于 86400 秒。

  • @user218867 是的,这是因为像 Alpine 这样的大多数轻量级容器操作系统只将日期打包到 busybox 中,但没有 GNU 日期来节省空间。 (2认同)

Abr*_*dez 5

我遇到了同样的问题,我尝试了上面的一些答案,也许它们没问题,但这些答案都没有解决我使用 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
Run Code Online (Sandbox Code Playgroud)

希望能帮助到你。


bas*_*080 5

Bash 最好通过利用管道 (|) 来编写。这应该会导致内存效率和并发(更快)处理。我会写以下内容:

seq 0 100 | xargs printf "20 Aug 2020 - %sdays\n" \
  | xargs -d '\n' -l date -d
Run Code Online (Sandbox Code Playgroud)

下面将打印日期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
Run Code Online (Sandbox Code Playgroud)

默认情况下,我们选择一次迭代过去 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
Run Code Online (Sandbox Code Playgroud)

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

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

$((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
Run Code Online (Sandbox Code Playgroud)