如何使用 Bash 遍历日期?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/28226229/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to loop through dates using Bash?
提问by stevek
I have such bash script:
我有这样的 bash 脚本:
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.
现在我想遍历一系列日期,例如 2015-01-01 到 2015-01-31。
How to achieve in Bash?
如何在 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.
好东西:在上一次运行完成之前,不应启动任何作业。在这种情况下,当 executeJobs.py 完成时,bash 提示$
将返回。
e.g. could I incorporate wait%1
in my loop?
例如,我可以合并wait%1
到我的循环中吗?
回答by Wintermute
Using GNU date:
使用 GNU 日期:
d=2015-01-01
while [ "$d" != 2015-02-20 ]; do
echo $d
d=$(date -I -d "$d + 1 day")
done
Note that because this uses string comparison, it requires full ISO 8601 notation of the edge dates (do not remove leading zeros). To check for valid input data and coerce it to a valid form if possible, you can use date
as well:
请注意,因为这使用字符串比较,所以它需要边缘日期的完整 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
One final addition: To check that $startdate
is before $enddate
, if you only expect dates between the years 1000 and 9999, you can simply use string comparison like this:
最后一个补充:要检查$startdate
之前$enddate
,如果您只希望日期介于 1000 年和 9999 年之间,则可以简单地使用字符串比较,如下所示:
while [[ "$d" < "$enddate" ]]; do
To be on the very safe side beyond the year 10000, when lexicographical comparison breaks down, use
为了在 10000 年后非常安全,当字典比较失败时,使用
while [ "$(date -d "$d" +%Y%m%d)" -lt "$(date -d "$enddate" +%Y%m%d)" ]; do
The expression $(date -d "$d" +%Y%m%d)
converts $d
to a numerical form, i.e., 2015-02-23
becomes 20150223
, and the idea is that dates in this form can be compared numerically.
该表达式$(date -d "$d" +%Y%m%d)
转换$d
为数字形式,即2015-02-23
变成20150223
,其想法是可以用数字比较这种形式的日期。
回答by kojiro
支撑扩展:
for i in 2015-01-{01..31} …
More:
更多的:
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
Ordered, if it matters:
订购,如果重要的话:
$ 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
Since it's unordered, you can just tack leap years on:
由于它是无序的,您可以只添加闰年:
$ 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
回答by Gilli
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
回答by jww
I needed to loop through dates on AIX, BSDs, Linux, OS X and Solaris. The date
command is one of the least portable and most miserable commands to use across platforms I have encountered. I found it easier to write a my_date
command that just worked everywhere.
我需要遍历 AIX、BSD、Linux、OS X 和 Solaris 上的日期。该date
命令是我遇到的跨平台使用的最不便携和最悲惨的命令之一。我发现编写一个my_date
在任何地方都有效的命令更容易。
The C program below takes a starting date, and adds or subtracts days from it. If no date is supplied, it adds or subtracts days from the current date.
下面的 C 程序需要一个开始日期,并从中添加或减去天数。如果未提供日期,则从当前日期增加或减少天数。
The my_date
command allows you to perform the following everywhere:
该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:
和 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;
}
回答by ankitbaldua
If one wants to loop from input date to any range below can be used, also it will print output in format of yyyyMMdd...
如果想要从输入日期循环到可以使用的任何范围,它也会以 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
回答by Abraham Hernandez
I had the same issue and I tried some of the above answers, maybe they are ok, but none of those answers fixed on what I was trying to do, using macOS.
我遇到了同样的问题,我尝试了上面的一些答案,也许它们没问题,但是这些答案都没有解决我使用 macOS 尝试做的事情。
I was trying to iterate over dates in the past, and the following is what worked for me:
我试图迭代过去的日期,以下是对我有用的:
#!/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
Hope it helps.
希望能帮助到你。
回答by bas080
Bash is best written by leveraging pipes(|). This should result in memory efficient and concurrent(faster) processing. I would write the following:
Bash 最好通过利用管道 (|) 来编写。这应该导致内存高效和并发(更快)处理。我会写以下内容:
seq 0 100 | xargs printf "20 Aug 2020 - %sdays\n" \
| xargs -d '\n' -l date -d
The following will print the date of 20 aug 2020
and print the dates of the 100 days before it.
以下将打印日期20 aug 2020
并打印它之前 100 天的日期。
This oneliner can be made into a utility.
这个oneliner可以做成一个实用程序。
#!/usr/bin/env bash
# date-range template <template>
template="${1:--%sdays}"
export LANG;
xargs printf "$template\n" | xargs -d '\n' -l date -d
By default we choose to iterate into the past 1 day at a time.
默认情况下,我们选择一次迭代到过去 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
Let's say we want to generate dates up to a certain date. We don't know yet how many iterations we need to get there. Let's say Tom was born 1 Jan 2001. We want to generate each date till a certain one. We can achieve this by using sed.
假设我们想要生成到某个日期的日期。我们还不知道需要多少次迭代才能到达那里。假设汤姆出生于 2001 年 1 月 1 日。我们希望生成每个日期直到某个日期。我们可以通过使用 sed 来实现这一点。
seq 0 $((2**63-1)) | date-range | sed '/.. Jan 2001 /q'
The
$((2**63-1))
trick is used to create a big integer.
该
$((2**63-1))
技巧用于创建一个大整数。
Once sed exits it will also exit the date-range utility.
一旦 sed 退出,它也将退出日期范围实用程序。
One can also iterate using a 3 month interval:
还可以使用 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
回答by Gellweiler
If you're stuck with busyboxdate, I've found working with timestamps to be the most reliable approach:
如果你被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
This will output:
这将输出:
2019-12-30
2019-12-31
2020-01-01
2020-01-02
2020-01-03
2020-01-04
Unix timestamp don't include leap seconds, so 1 day equals always exactly 86400 seconds.
Unix 时间戳不包括闰秒,所以 1 天总是正好等于 86400 秒。