在 Bash 中显示 运行 长申请的进度
Show progress for long running applications in Bash
我有一些程序涉及到嵌入式系统更新,需要按顺序运行,需要向单独的系统汇报进度
阶段是:
- 正在验证图像;
- 正在将图像文件解压为活动磁盘上的文件;和
- 正在将映像安装到备用磁盘。
之后,机器重新启动,备用磁盘成为活动磁盘。
目前,我们报告初始为 0%,验证结束时为 5%,解包结束时为 45%,安装结束时为 90%。当新映像启动 运行ning.
时,重新启动后会报告 100% 标记
现在,虽然我对这种方法非常满意,但客户希望看到更细粒度的更新状态报告。我们已明确告知他们任何此类报告大多是人为的,但他们认为有必要让他们的客户满意。
所以我正在寻找一种方法来报告基于粗略时间的进度,同时仍然让长时间的 运行ning 任务停止并完成他们的工作。
我们知道整个安装过程从开始到重启大约需要两分钟,每个阶段所花费的时间按照给定的百分比进行分配。
在 bash
中执行此操作的一种方法是 运行 一个单独的进程来进行进度报告并为其提供足够的信息以成功报告。这将涉及:
- 起始百分比;
- 结束百分比;
- 添加到每个周期百分比的增量;和
- 循环时间。
前两个很明显,它们提供了 get 报告的下限和上限。
另外两个允许您提供报告比率。例如,每秒增加 6% 将使用值 (delta = 6, cycle = 1)
,并且每秒增加 6%。
对于较慢的步长,比如每秒 0.25%,您可以提供 (delta = 1, cycle = 4)
,并且每四秒增加 1%。您实际上并没有 报告 亚秒级进度,只是随着时间的推移它会达到平均水平。
那么,这是怎么做到的呢?首先,让我们提供一个简单的函数来报告进度。在您的情况下,这应该替换为用于与您提到的 "separate system" 进行通信的任何内容。以下代码显示了如何执行此操作:
#!/usr/bin/env bash
doProgress() {
# Simply echo progress using whatever was passed. Date is included
# for debugging purposes.
echo "$(date +%H:%M:%S): %"
}
解决方案的"meat"是backgroundReporter()
函数,会根据前面提到的四个参数调用doProgress()
的函数:
backgroundReporter() {
# Get the four parameters.
currVal= ; maxVal= ; deltaVal= ; timeGap=
# If signalled to stop, output maximum percentage then exit.
# Note no output if you've already reached the maximum.
trap "[[ ${maxVal} -ne ${lastVal} ]] && doProg ${maxVal} ; exit" HUP
# Infinite loop until signalled.
while : ; do
# Wait for the duration, save current percentage, and
# calculate new percntage (capped at maximum).
sleep ${timeGap}
lastVal=${currVal}
(( currVal = (currVal + deltaVal > maxVal) ? maxVal : currVal + deltaVal ))
# Log only if it's changed.
[[ ${currVal} -ne ${lastVal} ]] && doProg ${currVal}
done
}
唯一棘手的一点是信号处理。 运行将此函数作为后台任务的调用者负责在该阶段完成后向其发送信号。
当然,还有一些基本的测试用例来展示这一点。首先,我们需要确保在退出时杀死子进程 运行ning backgroundReporter()
。当它不是 运行ning 时,执行此操作的命令将是 :
(一个简单的无操作),当它 是 [=67 时,将是 kill -HUP <pid>
=]宁:
killCmd=":" ; trap "${killCmd}" EXIT
然后我们报告零的初始进度并开始三个阶段。它们在这里比问题指定的要短,因为这仅用于演示目的:
doProg 0
# 0 -> 10% at 4%/sec, phase takes six seconds.
bgReport 0 10 4 1 & killCmd="kill -HUP $!" ; sleep 6 ; ${killCmd} ; killCmd=":" ; wait
# 10 -> 20% at 1%/2secs, phase takes six seconds.
bgReport 10 20 1 2 & killCmd="kill -HUP $!" ; sleep 6 ; ${killCmd} ; killCmd=":" ; wait
# 20 -> 100% at 30%/sec, phase takes five seconds.
bgReport 20 100 30 1 & killCmd="kill -HUP $!" ; sleep 5 ; ${killCmd} ; killCmd=":" ; wait
而且,正如您从样本 运行 中看到的那样,这三个阶段的效果与您预期的一样(右边的评论是我的):
13:15:29: 0%
13:15:30: 4% 4% per sec
13:15:31: 8%
13:15:32: 10% capped at 10% for remainder of 6-sec slot
13:15:37: 11% goes up 1% per 2 secs
13:15:39: 12%
13:15:41: 20% phase finishes, jumps immediately to max
13:15:42: 50% goes up 30% per second
13:15:43: 80%
13:15:44: 100% but capped at 100%
对于给定的实际次,两分钟,5%验证结束,45%解包结束,90%安装结束,以下比较合适(阶段稍微修改一下,这样比例更容易做到):
doProg 0
# Validation 0 -> 5%, 5 steps in 6 seconds (~ 1%/1s).
bgReport 0 5 1 1 & killCmd="kill -HUP $!"
validate
${killCmd} ; killCmd=":" ; wait
# Unpacking 5 -> 45%, 40 steps in 50 seconds (4%/5s).
bgReport 5 45 4 5 & killCmd="kill -HUP $!"
unpack
${killCmd} ; killCmd=":" ; wait
# Installing 45 -> 95%, 50 steps in 64 seconds (~ 5%/6s)
bgReport 45 95 5 6 & killCmd="kill -HUP $!"
install
${killCmd} ; killCmd=":" ; wait
使用这些比率,您可以保证每(最多)六秒获得一次进度更新,这与原始方法不同,原始方法会在 45% 的状态下保持一分钟然后立即跳到 95%。
我有一些程序涉及到嵌入式系统更新,需要按顺序运行,需要向单独的系统汇报进度
阶段是:
- 正在验证图像;
- 正在将图像文件解压为活动磁盘上的文件;和
- 正在将映像安装到备用磁盘。
之后,机器重新启动,备用磁盘成为活动磁盘。
目前,我们报告初始为 0%,验证结束时为 5%,解包结束时为 45%,安装结束时为 90%。当新映像启动 运行ning.
时,重新启动后会报告 100% 标记现在,虽然我对这种方法非常满意,但客户希望看到更细粒度的更新状态报告。我们已明确告知他们任何此类报告大多是人为的,但他们认为有必要让他们的客户满意。
所以我正在寻找一种方法来报告基于粗略时间的进度,同时仍然让长时间的 运行ning 任务停止并完成他们的工作。
我们知道整个安装过程从开始到重启大约需要两分钟,每个阶段所花费的时间按照给定的百分比进行分配。
在 bash
中执行此操作的一种方法是 运行 一个单独的进程来进行进度报告并为其提供足够的信息以成功报告。这将涉及:
- 起始百分比;
- 结束百分比;
- 添加到每个周期百分比的增量;和
- 循环时间。
前两个很明显,它们提供了 get 报告的下限和上限。
另外两个允许您提供报告比率。例如,每秒增加 6% 将使用值 (delta = 6, cycle = 1)
,并且每秒增加 6%。
对于较慢的步长,比如每秒 0.25%,您可以提供 (delta = 1, cycle = 4)
,并且每四秒增加 1%。您实际上并没有 报告 亚秒级进度,只是随着时间的推移它会达到平均水平。
那么,这是怎么做到的呢?首先,让我们提供一个简单的函数来报告进度。在您的情况下,这应该替换为用于与您提到的 "separate system" 进行通信的任何内容。以下代码显示了如何执行此操作:
#!/usr/bin/env bash
doProgress() {
# Simply echo progress using whatever was passed. Date is included
# for debugging purposes.
echo "$(date +%H:%M:%S): %"
}
解决方案的"meat"是backgroundReporter()
函数,会根据前面提到的四个参数调用doProgress()
的函数:
backgroundReporter() {
# Get the four parameters.
currVal= ; maxVal= ; deltaVal= ; timeGap=
# If signalled to stop, output maximum percentage then exit.
# Note no output if you've already reached the maximum.
trap "[[ ${maxVal} -ne ${lastVal} ]] && doProg ${maxVal} ; exit" HUP
# Infinite loop until signalled.
while : ; do
# Wait for the duration, save current percentage, and
# calculate new percntage (capped at maximum).
sleep ${timeGap}
lastVal=${currVal}
(( currVal = (currVal + deltaVal > maxVal) ? maxVal : currVal + deltaVal ))
# Log only if it's changed.
[[ ${currVal} -ne ${lastVal} ]] && doProg ${currVal}
done
}
唯一棘手的一点是信号处理。 运行将此函数作为后台任务的调用者负责在该阶段完成后向其发送信号。
当然,还有一些基本的测试用例来展示这一点。首先,我们需要确保在退出时杀死子进程 运行ning backgroundReporter()
。当它不是 运行ning 时,执行此操作的命令将是 :
(一个简单的无操作),当它 是 [=67 时,将是 kill -HUP <pid>
=]宁:
killCmd=":" ; trap "${killCmd}" EXIT
然后我们报告零的初始进度并开始三个阶段。它们在这里比问题指定的要短,因为这仅用于演示目的:
doProg 0
# 0 -> 10% at 4%/sec, phase takes six seconds.
bgReport 0 10 4 1 & killCmd="kill -HUP $!" ; sleep 6 ; ${killCmd} ; killCmd=":" ; wait
# 10 -> 20% at 1%/2secs, phase takes six seconds.
bgReport 10 20 1 2 & killCmd="kill -HUP $!" ; sleep 6 ; ${killCmd} ; killCmd=":" ; wait
# 20 -> 100% at 30%/sec, phase takes five seconds.
bgReport 20 100 30 1 & killCmd="kill -HUP $!" ; sleep 5 ; ${killCmd} ; killCmd=":" ; wait
而且,正如您从样本 运行 中看到的那样,这三个阶段的效果与您预期的一样(右边的评论是我的):
13:15:29: 0%
13:15:30: 4% 4% per sec
13:15:31: 8%
13:15:32: 10% capped at 10% for remainder of 6-sec slot
13:15:37: 11% goes up 1% per 2 secs
13:15:39: 12%
13:15:41: 20% phase finishes, jumps immediately to max
13:15:42: 50% goes up 30% per second
13:15:43: 80%
13:15:44: 100% but capped at 100%
对于给定的实际次,两分钟,5%验证结束,45%解包结束,90%安装结束,以下比较合适(阶段稍微修改一下,这样比例更容易做到):
doProg 0
# Validation 0 -> 5%, 5 steps in 6 seconds (~ 1%/1s).
bgReport 0 5 1 1 & killCmd="kill -HUP $!"
validate
${killCmd} ; killCmd=":" ; wait
# Unpacking 5 -> 45%, 40 steps in 50 seconds (4%/5s).
bgReport 5 45 4 5 & killCmd="kill -HUP $!"
unpack
${killCmd} ; killCmd=":" ; wait
# Installing 45 -> 95%, 50 steps in 64 seconds (~ 5%/6s)
bgReport 45 95 5 6 & killCmd="kill -HUP $!"
install
${killCmd} ; killCmd=":" ; wait
使用这些比率,您可以保证每(最多)六秒获得一次进度更新,这与原始方法不同,原始方法会在 45% 的状态下保持一分钟然后立即跳到 95%。