我正在用Shell脚本编写代码以加载特定范围内的数据,但是它并不会停留在我想要的数据上,而是超越了该范围。下面是我的shell脚本代码。
j=20180329
while [ $j -le 20180404]
do
我的问题是我的循环在日期20180331之后运行至20180399,然后转到20180401。 我希望它从20180331到20180401,而不是20180332等
答案 0 :(得分:1)
您的请求代表shell
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
但是我不喜欢这样,因为如果时间格式更改,这可能会成为问题。
在bash和shell下测试为dash
和busybox
。
(使用 date (GNU coreutils) 8.26
。
printf
在bash下,您可以使用所谓的 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)
然后使用bash内置的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函数,用于添加后台命令以减少分叉。
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
格式的字符串来填充它,以便它始终知道如何解析它。