如何在Shell脚本中设置日期范围

时间:2019-05-02 12:36:03

标签: shell

我正在用Shell脚本编写代码以加载特定范围内的数据,但是它并不会停留在我想要的数据上,而是超越了该范围。下面是我的shell脚本代码。

j=20180329
while [ $j -le 20180404]
do

我的问题是我的循环在日期20180331之后运行至20180399,然后转到20180401。 我希望它从20180331到20180401,而不是20180332等

2 个答案:

答案 0 :(得分:1)

一个简单的问题,三个不是那么简短的答案...

您的请求代表

兼容答案优先

j=20180329
while [ "$j" != "20180405" ] ;do
    echo $j
    j=`date -d "$j +1 day" +%Y%m%d`
done

注意:我一天后使用 ,因为while的条件是基于平等的!当然,将YYYYMMDD日期解释为整数也可以:

注释2 关心时区设置TZ=UTC,请参见问题 ...

j=20180329
while [ $j -le 20180404 ] ;do
    echo $j
    j=`TZ=UTC date -d "$j +1 day" +%Y%m%d`
done

但是我不喜欢这样,因为如果时间格式更改,这可能会成为问题。

下测试为dashbusybox
(使用 date (GNU coreutils) 8.26

日期通过printf

下,您可以使用所谓的 bashisms

将日期转换为整数,但通过一个 fork 将两个日期转换为:

{
    read start;
    read end
} < <(date -f - +%s <<eof
20180329
20180404
eof
)

start=20180329
end=20180404
{ read start;read end;} < <(date -f - +%s <<<$start$'\n'$end)

然后使用内置的printf命令(注意:通常每天$[24*60*60]-> 86400秒)

for (( i=start ; i<=end ; i+=86400 )) ;do
    printf "%(%Y%m%d)T\n" $i
done

时区问题!

警告夏季冬季时间有关:

作为功能

dayRange() { 
    local dR_Start dR_End dR_Crt
    { 
        read dR_Start
        read dR_End
    } < <(date -f - +%s <<<${1:-yesterday}$'\n'${2:-tomorrow})
    for ((dR_Crt=dR_Start ; dR_Crt<=dR_End ; dR_Crt+=86400 )) ;do
        printf "%(%Y%m%d)T\n" $dR_Crt
    done
}

显示问题:

TZ=CET dayRange 20181026 20181030
20181026
20181027
20181028
20181028
20181029

printf "%(%Y%m%d)T\n" $dR_Crt代替printf "%(%Y%m%dT%H%M)T\n" $dR_Crt可能会有所帮助:

20181026T0000
20181027T0000
20181028T0000
20181028T2300
20181029T2300

为避免此问题,您只需在功能开始时本地化 TZ=UTC

    local dR_Start dR_End dR_Crt TZ=UTC

功能的最后一步:避免无用的叉子

为了提高性能,我尝试减少派生,避免使用如下语法:

    for day in $(dayRange 20180329 20180404);do ...
    # or
    mapfile range < <(dayRange 20180329 20180404)

我使用函数的功能直接设置提交的变量:

我的目的是

dayRange() { # <start> <end> <result varname>
    local dR_Start dR_End dR_Crt dR_Day TZ=UTC
    declare -a dR_Var='()'
    { 
        read dR_Start
        read dR_End
    } < <(date -f - +%s <<<${1:-yesterday}$'\n'${2:-tomorrow})
    for ((dR_Crt=dR_Start ; dR_Crt<=dR_End ; dR_Crt+=86400 )) ;do
        printf -v dR_Day "%(%Y%m%d)T\n" $dR_Crt
        dR_Var+=($dR_Day)
    done
    printf -v ${3:-dRange} "%s" "${dR_Var[*]}"
}

然后进行快速的小错误测试:

TZ=CET dayRange 20181026 20181030 bugTest
printf "%s\n" $bugTest 
20181026
20181027
20181028
20181029
20181030

看起来不错。可以这样使用:

dayRange 20180329 20180405 myrange
for day in $myrange ;do
    echo "Doing something with string: '$day'."
done

使用 shell-connector

的替代方法

有一个shell函数,用于添加后台命令以减少分叉。

wget https://f-hauri.ch/vrac/shell_connector.sh
. shell_connector.sh

启动背景date +%Y%m%d并进行测试:@0必须回答19700101

newConnector /bin/date '-f - +%Y%m%d' @0 19700101

然后

j=20190329
while [ $j -le 20190404 ] ;do
    echo $j; myDate "$j +1 day" j
done

小板凳

让我们尝试3年范围:

j=20160329
time while [ $j -le 20190328 ] ;do
    echo $j;j=`TZ=UTC date -d "$j +1 day" +%Y%m%d`
done | wc
1095    1095    9855

real    0m1.887s
user    0m0.076s
sys     0m0.208s

我的系统上超过1秒的时间...当然,有1095个前叉!

time { dayRange 20160329 20190328 foo && printf "%s\n" $foo | wc ;}
1095    1095    9855

real    0m0.061s
user    0m0.024s
sys     0m0.012s

仅1个分叉,然后bash内置->少于0.1秒...

并具有newConnector功能:

j=20160329
time while [ $j -le 20190328 ] ;do echo $j
    myDate "$j +1 day" j
  done | wc
   1095    1095    9855

real    0m0.109s
user    0m0.084s
sys     0m0.008s

不如使用内置整数快,但还是非常快。

答案 1 :(得分:0)

使用自时期以来的秒数存储最大和最小日期。不要使用日期-日期不准确(GMT,UTC等)。从纪元起使用秒数。然后以一天中的秒数增加变量-即。 24 * 60 * 60秒。在循环中,您可以使用date --date=@<number>将自纪元以来的秒数转换回人类可读的日期。以下将适用于POSIX shell和GNU的日期实用性:

from=$(date --date='2018/04/04 00:00:00' +%s)
until=$(date --date='2018/04/07 00:00:00' +%s)

counter="$from"
while [ "$counter" -le "$until" ]; do
    j=$(date --date=@"$counter" +%Y%m%d)

    # do somth with j
    echo $j

    counter=$((counter + 24 * 60 * 60))
done

GNU的date在解析它的--date=FORMAT格式字符串时有点奇怪。我建议始终使用%Y/%m/%d %H/%M/%S格式的字符串来填充它,以便它始终知道如何解析它。