【动机】
从上篇综合进度条中可以看到, 如果每次运行任务(命令或函数), 都需要进行逐个进度条, 变换符号, 耗时函数的调用, 然后再运行任务, 则不可谓不繁. 如何解决?
【意图】
将要执行的命令或函数, 进度条等必须的选项作为参数, 其余使用默认, 封装得到适用于任何任务(耗时长或短或极短的命令或函数)调用的函数.
【实现】
在 system.sh 中, 新建函数 perform, 如下:
:<<COMMENT
为任意命令添加
1. 操作前提示
2. 命令失败后提示并退出
3. 进度
--prompt(-p) 提示用语, 默认 "start an unknow operation..."
--cmdline(-l) 要执行的命令行(包括全部参数), 默认 ":", 即空操作
--amount(-t) 目标数量(用于显示百分比进度), 默认 100
--calc(-c) 用来计算当前数量的函数(或命令行, 用于显示百分比进度), 可选
--delay(-d) 连续两次打印之间的延时, 默认 1. 单位秒
--use-op(-o) 标旗参数. 指示 -c(--calc) 指定的函数(命令行), 是否使用输出参数获取当前数量.
出现表示使用输出参数; 否则表示使用返回值
--erase(-e) 标旗参数. 任务结束后, 是否擦除进度/耗时/字符. 出现表示擦除, 否则不擦除
用法: perform -p <prompt='...'> -l <cmdline=:> -t <amount:100> -c <calc> -d <delay=1> -e
要求, 将要执行的方法, 应有确定的退出码, 表示成功与否, 否则默认为成功(0)
注意:
1. 要执行的任务, 已兼容 docker 命令, 故 dockerX 可以退休了.
2. 如果要显示百分比而不是仅仅左右来回跑动的进度, 则 -c 必须
3. 延时时长对于不同的任务, 作用不同. 为了适当展示进度, 建议:
1) 对于单向进度,
a. 操作比较耗时(比如上传到网络或从网络下载较多文件), 延时时长可设置大些, 可考虑 20 - 30 秒
b. 操作很快时, 如果不在乎进度可能会跳变, 考虑 2-3 秒; 在乎, 则考虑 0.5 左右
2) 对于循环进度,
a. 任务执行比较耗时, 延时可稍长, 推荐 1.5-2.6
b. 任务执行比较快, 延时可稍短, 推荐 0.2-0.5, 但如果太短, 进度可能表现闪烁
COMMENT
perform(){
eval $PASX
local prompt cmdline amount calc delay
params " 'prompt p' 'start an unknow operation...'" \
" 'cmdline l' ':' " \
" 'amount t' 100 " \
" 'calc c' " \
" 'delay d' 1"
local pair_use_op pair_erase
param_appear use_op o && pair_use_op='-o'
param_appear erase e && pair_erase='-e'
local bar_id vary_id time_id
# 后台进度条, 变换符号, 消耗时间 R_SYMB_W 等常数参见 progress_bar_v2.sh
aop info right "$prompt"
progress_bar -t $amount -d $delay -m "$calc" $pair_use_op -x "33" $pair_erase \
--align right -l $[L_TIME_W+PB_GAP+1] -w $[$(tput cols)-R_SYMB_W-L_TIME_W-2*PB_GAP] &
bar_id=$!
elapsing_time -c "32" -i 0.5 $pair_erase & #
time_id=$!
vary_symbol -c "31;5" -i 0.25 $pair_erase & #
vary_id=$!
# 展平(将命令行可能包含的回车修正为空格)的同时, 保持空格个数(即多个空格不浓缩)
cmdline=${cmdline//$'\n'/ }
[ "$__DEBUG__" ] && aop notice left "eval $cmdline"
# 兼顾某些用 eval 容易失败的命令, 例如远程执行的 test
eval "$cmdline" || $cmdline
exitcode=$?
sleep 0.8
# 清除后台进程
#[ "$calc" ] && wait $bar_id || kill $bar_id
kill $bar_id $vary_id $time_id &>/dev/null
log_info "prepare exit with task's original exitcode=<$exitcode>"
# 返回任务的退出码
return $exitcode
}
注意, 选项参数名称, 可以是字母型, 单词型, 前面的连接线数量, 只要保证至少为 1 即可, 也就是说, -t 与 —–t, -delay 与 —delay 效果是一致的, 这扇方便之门, 来自于改写后的模块: parameter_v2.sh. 注意, 进度条的进度块的色彩和字符, 均为随机生成. 其中用到的常数和子函数比较多, 但函数调用确很简单, 一般只需要区分需要的是百分比进度条还是往返进度条即可.
【测试】
新建 test.sh, 键入以下代码:
clear
declare -r INCR=3
_assign(){
local v_name=$1
sleep 0.2
eval "let $v_name+=$INCR"
}
local files total=45
# 带任意参数, 则模拟打印输出
_mock_copy_file(){
files=0
while [ $files -lt $total ];do
sleep 0.8
files=$(available_integer $[files+INCR] $total 0 $total)
info -e "copy files: $files completed $(random_string 56)"
done
}
way1(){
perform -p "start to copy file..." -l "_mock_copy_file" -t $total -c _assign --use-op -d 0.2
read -p "another progress bar..." -t 3 || :
perform -p "start to copy file, without rate..." -cmdline "_mock_copy_file" -t $total -d 0.01
}
# 利用从百度网盘上传/下载文件测试百分比进度条
way2(){
clear
local local_src_dir="$ROOT_DIR_PATH/test/lnx-code"
local baidu_tgt_dir='test/lnx-code'
local local_tgt_dir="$ROOT_DIR_PATH/test/temp-code"
timeout 5 bypy info &>/dev/null
if [ $? -eq 0 ]; then
read -p "press enter to start upload..." -t 3 || :
local local_total # &>/dev/null
perform -p "计算要上传到百度网盘的文件数量, 请稍候..." -l "local_total=\$(local_files \"$local_src_dir\")" -d 0.1
perform -p "现在上传..." -l "bypy upload $local_src_dir $baidu_tgt_dir" \
-t $local_total -c "baidu_files $baidu_tgt_dir" -d 0.3
read -p "press enter to start download..." -t 3 || :
local baidu_total # &>/dev/null
perform -p "计算要下载到本地的百度网盘的文件数量, 请稍候..." -l "baidu_total=\$(baidu_files \"$baidu_tgt_dir\")" -d 0.1
perform -p "现在下载..." -l "bypy download $baidu_tgt_dir $local_tgt_dir" \
-t $baidu_total -c "local_files $local_tgt_dir" -d 0.3
read -p "press enter to delete them..." -t 3 || :
perform -p "删除下载的文件..." -l "rm -rf $local_tgt_dir" -d 0.1
perform -p "删除百度网盘上的测试文件..." -l "bypy delete $baidu_tgt_dir" -d 0.1
else
aop error center "baidu not available."
fi
}
#way1
#way2
unset -f way1 way2 _assign
注意, 上述测试代码, 用到 file.sh 中的函数 baidu_files 和 local_files, 用来获取网盘和本地文件的数量, 而前者内部又用到 _baidu_files 函数(关于 bypy 的安装和使用, 网上教程很多, 在此不便赘述):
:<<COMMENT
获取本地计算机上的目录下的所有文件数量
$1: path 目录
用法: local_files <dir_path>
COMMENT
local_files(){
#sleep 14
local count=$(find "$1" -type f 2>/dev/null | wc -l)
[ "$__DEBUG__" ] && log_info "<$1> files:<$count>"
echo $count
}
:<<COMMENT
外部调用
获取百度网盘上指定目录下的所有文件, 该目录必然是位于
我的网盘>我的应用数据>bypy
为根
$1: path 目录路径, 不带末尾斜杆, 也不包括上面说的根.
例如: 我的网盘>我的应用数据>bypy>test>good>, 则
$1 为 test/good
用法: baidu_files <baidu_dir_path>
COMMENT
baidu_files(){
local sum
[ "$__DEBUG__" ] && log_info "start calculate files for <$1>......"
_baidu_files "$1" sum
[ "$__DEBUG__" ] && log_info "summary, baidu wangpan <$1> files:<$sum>"
echo $sum
}
:<<COMMENT
内部调用
获取百度网盘上指定目录下的所有文件, 该目录必然是位以
我的网盘>我的应用数据>bypy
为根
$1: path 目录路径, 不带末尾斜杆, 也不包括上面说的根.
例如: 我的网盘>我的应用数据>bypy>test>
$2: v_name: 累加器变量名称, 在该变量名下不断累加文件数量
usage: _baidu_files <path> <accum_name>
COMMENT
_baidu_files(){
local files=$(bypy list "$1" | grep '^F *' | wc -l)
[ "$__DEBUG__" ] && log_info "---path<$1>:<$files>"
eval "$2=$[${!2}+$files]"
# 应对连续的多个空格. 并且不论相邻一个还是多个空格都能能完整保留
local dir_names=$(bypy list "$1" | grep '^D ' | sed -E \
's/^D\s+(.+)\s+[0-9]\s+[0-9]{4}-[0-9]{2}-[0-9]{2},\s+[0-9]{2}:[0-9]{2}:[0-9]{2}\s*$/\1/')
# 由于 * 不可能出现在目录或文件名里, 故将空格临时替换为 *,相当于用 * 记住空格的位置和数量,
# 枚举时将只能以换行符分割, 枚举后, 再还原 * 为空格, 可达到目的.
local org_n
dir_names=${dir_names// /*}
for name in $dir_names;do
org_n=${name//\*/ }
_baidu_files "$1/$org_n" $2
done
}
【结果】
- 开放 way1 注释, 其中的第一个 perform 是借用有限循环来模拟拷贝文件, 故此时的百分比和循环的进度可能不会严格对应. 第二个 perform 则是使用往返进度条, 当模拟拷贝文件的进程结束, 进度条也结束.
2. 开放 way2 注释, 使用百度网盘上传下载文件测试, 以此测试百分比进度条与函数调用的结合.