我正在 shell 脚本中编写代码来加载特定范围内的数据,但它不会停止在我想要的数据处,而是超出了该范围。下面是我的shell脚本代码。
j=20180329
while [ $j -le 20180404]
do
我有一个问题,我的循环在日期 20180331 之后运行到 20180399,然后转到 20180401。 我希望它从 20180331 到 20180401。而不是 20180332 等等
最佳答案
一个简单的问题,3 个以上不那么简短的答案...
正如您的请求代表 shell
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
但我不喜欢这样,因为如果时间格式发生变化,这可能会成为一个问题。
在 bash 下测试和 shell如 dash
和 busybox
。
(使用日期(GNU coreutils)8.26
。
1.2 POSIX shell下最小化fork
使用之前bash bashisms,这是在任何 POSIX shell 下执行此操作的一种方法:
POSIX shell 的强大之处在于我们可以使用非常简单的转换器,例如日期
,并对结果执行条件:
#!/usr/bin/env sh
tempdir=$(mktemp -d)
datein="$tempdir/datein"
dateout="$tempdir/dateout"
mkfifo "$datein" "$dateout"
exec 5<>"$datein"
exec 6<>"$dateout"
stdbuf -i0 -o0 date -f - <"$datein" >"$dateout" +'%Y%m%d' &
datepid=$!
echo "$2" >&5
read -r end <&6
echo "$1" >&5
read -r crt <&6
while [ "$crt" -le "$end" ];do
echo $crt
echo "$crt +1 day" >&5
read -r crt <&6
done
exec 5>&-
exec 6<&-
kill "$datepid"
rm -fR "$tempdir"
然后
daterange.sh 20180329 20180404
20180329
20180330
20180331
20180401
20180402
20180403
20180404
2。 bash通过 printf
获取日期
下bash ,你可以使用所谓的bashisms:
将日期转换为整数 Epoch (Unix time) ,但是通过一个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
3。时区问题!!
警告夏季与冬季时间存在问题:
作为函数
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
函数的最后一步:避免无用的 fork
为了提高性能,我尝试减少 fork ,避免使用如下语法:
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
2.2 使用 shell-connector 的替代方案
有一个shell函数可以添加后台命令以减少fork。
wget https://f-hauri.ch/vrac/shell_connector.sh
. shell_connector.sh
启动后台日期+%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.++小板凳
让我们尝试一下 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 个 fork !
time { dayRange 20160329 20190328 foo && printf "%s\n" $foo | wc ;}
1095 1095 9855
real 0m0.061s
user 0m0.024s
sys 0m0.012s
只有 1 个 fork,然后是 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
虽然不如使用内置整数快,但还是很快。
关于shell - 如何在shell脚本中设置日期范围,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55952605/