【动机】
经常在线安装软件包的朋友, 一定对下载/安装时的进度条印象深刻. 本节起, 一共使用四节的篇幅, 与大家分享几种进度条的制作与使用.
【预备常量和函数】
1.进度函数可能用到的常量, 可定义在 progress_bar_v2.sh 中:
declare -r PB_MIN_WIDTH=1 # 最小进度宽度
declare -r PB_DELAY=.2 # 进度条变化之间的默认延时
declare -r PB_STEP=1 # 进度条的默认步进
declare -r PB_TOTAL=100 # 进度 100% 时的反应的默认进度值
declare -r PB_TURNING='|/-\' # 表示进度的翻转字符串
declare -r PB_GAP=1 # 默认在时间消耗/进度条/旋转符号两两之间的间隙
declare -r L_TIME_W=8 # 默认的屏幕第一列时间消耗显示占用宽度
declare -r R_SYMB_W=1 # 默认的屏幕倒数第一列旋转字符占用宽度
2.随机颜色字符串函数 random_color 和 随机符号函数 random_symbol, 位于 data.sh:
:<<COMMENT
在 __COLOR_STRS__ 数组中, 随机产生一个颜色字符串
用法: random_color
COMMENT
random_color(){
local idx=$(random_from_range 0 $[${#__COLOR_STRS__[@]}-1])
echo ${__COLOR_STRS__[$idx]}
}
:<<COMMENT
在 __SYMBOLS__ 数组中, 随机产生一个符号
用法: random_symbol
COMMENT
random_symbol(){
local idx=$(random_from_range 0 $[${#__SYMBOLS__[@]}-1])
echo ${__SYMBOLS__[$idx]}
}
其中, __COLOR_STRS__ 和 __SYMBOLS__ 数组定义, 也位于 data.sh:
# 还可以继续添加其他颜色字符串
declare -r __COLOR_STRS__=(
'36;4;41' '36;4;43' '36;4;44' '36;4;45' '36;4;47'
'44;4;30' '44;4;31' '44;4;32' '44;4;33' '44;4;35' '44;4;36' '44;4;37'
'36;5;41' '36;5;43' '36;5;44' '36;5;45' '36;5;47'
'44;5;30' '44;5;31' '44;5;32' '44;5;33' '44;5;35' '44;5;36' '44;5;37'
'36;7;41' '36;7;43' '36;7;44' '36;7;45' '36;7;47'
'44;7;30' '44;7;31' '44;7;32' '44;7;33' '44;7;35' '44;7;36' '44;7;37'
'36;9;41' '36;9;43' '36;9;44' '36;9;45' '36;9;47'
'44;9;30' '44;9;31' '44;9;32' '44;9;33' '44;9;35' '44;9;36' '44;9;37'
'36;2;41' '36;2;43' '36;2;44' '36;2;45' '36;2;47'
'44;2;30' '44;2;31' '44;2;32' '44;2;33' '44;2;35' '44;2;36' '44;2;37'
)
# 还可以继续添加其他符号, 但是不用 '*' 星号,
declare -r __SYMBOLS__=(
'~' '!' '@' '#' '$' '%' '^' '&' ',' '+' '-' ' ' '=' '\' '/'
)
3. 清场函数: 当进度条(被)终止时的清理操作
pb_clean_spot(){
trap "" SIGTERM SIGINT SIGKILL
printf "\e[?25h"
ending=y
#tip -e "\e[amating is over. \e[u"
return 0
}
【进度函数】
进度函数progress_bar, 包含步进式往返进度条, 整体式往返进度条, 百分比进度条. 使用参数挑选进度条种类. 函数位于 progress_bar_v2.sh. 详细用法参看注释:
:<<COMMENT
进度条展示, 分三种: 整体型进度条 overall, 步进型进度条 step 和百分比进度条 rate, 前两者为往返型.
通用参数
-y: --style 进度条类型. 有效值为 overall | step | rate, 默认 overall.
注意: 如果后面的 -m 或 --assign-fun 非空, 则此处的 style 配置被忽略, 强制设置为 rate.
-d --delay 进度条宽度增长或缩短前延时的秒数, 可以是小数或整数, 默认 PB_DELAY
-s --symbols 一个或多个符号, 不支持占列为2及以上的符号. 默认为一个随机符号
1) 对于 overall step 往返进度条, 长度大于步进值则取头部几个(数量等于步进值);
2) 对于 rate 百分比进度条, 仅第一个字符有效.
-c: --p-clrs 颜色字符串, 指示进度条的: 前景/背景/字效, 中间用分号隔开, 例如 "41;36;4", 默认为随机颜色字符串
-r: --row 显示的行坐标( 1 起始), 默认值 SCREEN_BOTTOM_LINE(屏幕最底部一行)
-l: --col 显示的列坐标( 1 起始), 默认值 1 (屏幕最左侧一列)
-w: --width 总宽度(包含进度条, 如果是百分比进度条, 还包含描述文本), 默认为运行时控制台总列数,
输入值超出范围 PB_MIN_WIDTH - SCREEN_WIDTH, 强制为默认值
-e, --erase 终止前, 是否清除残余显示. 这是个标记参数, 无标记或显式标记为 no, 则不清除; 否则(包括设置空值)表示清除
style = overall | step 往返进度条的有效参数
-p: --step 步进值, 默认 PB_STEP
style = rate 百分比进度条的有效参数
-x: --t-clrs 百分比描述文本的颜色字符串, 默认为随机颜色
-t: --total 目标数量, 默认 PB_TOTAL.
-m: --assign-fun 为当前数量赋值的函数, 签名: 或者末尾带一个输出参数, 或者使用返回值. 由下面的 -o 决定.
进度函数将以此数量与目标数量的商作为百分数显示.
-o: --use-op assign-fun 这是一个标记参数, 表示是否使用输出参数赋值.
无标记或显式标记为 no, 则表示使用返回值; 否则(包括设置空值)表示使用输出参数
-a: --align 百分比字符串, 与进度条的位置关系, 可用值 left center right. 默认为 left.
注意居中时, 字符串会覆盖一部分进度条
-f: --force 自行结束或被进程被中断, 是否强制打印 100% 进度, 以便操作者知悉后再结束. 标记参数, 无标记或显示标记为 no, 则不打印;
否则(包括设置空值)表示打印.
使用方法: progress_bar -d <delay=PD_DELAY> -s <str=random> -c <p_clrs=random> \
-r <row=SCREEN_BOTTOM_LINE> -l <col=SCREEN_LEFT_COL> -w <width=SCREEN_WIDTH> -e <erase?> \
-y <style=overall> -p <step=PB_STEP> \ # 往返进度条
-x <t_clrs=random> -t <total=PB_TOTAL> -m <assign_fun> # 百分比进度条
为了表达简单清晰, 用法中未提及对应的长选项参数.
说明:
1. symbols, 对于 rate 百分比进度条, 强制只保留第一个字符, 其余被忽略.
2. 步进型 (step) 往返进度条效率较高, 但可能被中间输出打断; 整体型 (overall) 则与之相反.
3. 最简化的使用方式, 注意生产中需要做后台进程运行:
1) 步进型往返进度条: progress_bar -y step &
2) 整体型往返进度条: progress_bar &
3) 百分比进度条: progress_bar -y rate -t 356 -m getAmount &
COMMENT
progress_bar(){
# 隐藏光标, 初始化参数
_pb_init(){
tput civis
style=$(available_range "$style" overall step overall rate)
[ "$assign_fun" ] && style=rate
# 修正位置参数, 不出界
limit_bound row 1 $sh
limit_bound col 1 $sw
# 协调好宽度和列位置, 防止进度条换行
max_w=$[sw-col+1] # 例如 col=2, sw=90时, max_w=89, 而不是88, 因为还有一个起始列包含在宽度中
width=$(available_integer "$width" $max_w $PB_MIN_WIDTH $max_w )
max_col=$[col+width]
#log_info "max_col:<$max_col>"
# 修正字符串长度为 step, 过长则截断尾部, 过短则在右边添加空格
printf -v symbols "%-${step}.${step}s" "$symbols"
#log_info "bar color:<$p_clrs>, symbols:<$symbols>"
}
# 步进式往返进度条
pb_step(){
local cur_col=$col gap rest=0
printf "\r"
while [ -z "$ending" ] ; do
while [ -z "$ending" -a $cur_col -lt $max_col ]; do
let gap=max_col-cur_col
[ $gap -gt $step ] && rest=$step || rest=$gap
printf "\e[s\e[${p_clrs}m\e[$row;${cur_col}H%.${rest}s\e[0m\e[u" "$symbols"
let cur_col+=rest
sleep $delay
done
while [ -z "$ending" -a $cur_col -gt $col ];do
let cur_col-=step
[ $cur_col -lt $col ] && cur_col=$col
printf "\e[s\e[$row;${cur_col}H%${step}.${step}s\e[u"
sleep $delay
done
done
}
# 整体式往返进度条
pb_overall(){
local bar rest_len forward=1
while [ -z "$ending" ] ; do
if [ $forward -eq 1 ];then
bar+="$symbols"
printf -v bar "%.${width}s" "$bar" # 防止右出界
[ ${#bar} -eq $width ] && forward=0
else
rest_len=$(ensure_natural 0 $[${#bar}-step]) # 防止左出界
bar=${bar:0:$rest_len}
[ $rest_len -eq 0 ] && forward=1
fi
printf "\e[s\e[$row;${col}H%${width}s\e[${p_clrs}m\e[${col}G%s\e[0m\e[u" '' "$bar"
sleep $delay
done
}
# 百分比进度条
pb_rate(){
# 展平
printf -v assign_fun "%s " $assign_fun
#assign_fun=$(printf "%s " $assign_fun | sed 's/ $//') # 末尾空格不能留
# 真的吗, 现在故意加些空格
assign_fun="$assign_fun "
align=$(available_range $align left left center right)
local symb=${symbols:0:1}
local n_cur bar_len p_cur bar desc
local desc_col desc_len bar_col bar_bound neg
# desc 字符串显示的最大长度
# 例如 total=858, 则 100.00% 时, 呈现为: 100.00%|858/858, 最大需要:
desc_len=$[${#total}*2+9]
# 如果进度条太短, 由于 desc 字符串的存在, 可能会被撑长, 继而折行显示
if [ $[width-desc_len] -gt 1 ]; then
# 当 desc 对齐非 center 时, bar_bound 需要留出相应宽度
case "$align" in
'left')
desc_col=$col
neg='-' # printf 打印时用来确认是否左对齐
bar_col=$[col+desc_len]
bar_bound=$[width-desc_len]
;;
'right')
desc_col=$[max_col-desc_len]
bar_col=$col
bar_bound=$[width-desc_len]
;;
'center')
desc_col=$[col+(width-desc_len)/2]
bar_col=$col
bar_bound=$width
;;
*)
log_error "unknown align style:<$align>"
return 1
esac
n_cur=0 bar_len=0
if [ $total -gt 0 ]; then
#log_info "assign_fun:<$assign_fun>,use_op:<$use_op>"
#logx_ok "assign:<$assign_fun>"
while [ $bar_len -lt $bar_bound -a -z "$ending" ]; do
# 依次为: 当前值, 百分比, 折合得到的进度长度, 代表当前进度的 bar 子串, 描述字符串
[ "$use_op" != no ] && eval "$assign_fun n_cur" || n_cur=$(eval "$assign_fun" 2>/dev/null)
[ $n_cur -ge $total ] && n_cur=$total
p_cur=$(bc -l <<< "scale=2; 100.0*$n_cur/$total")
bar_len=$(bc -l <<< "scale=0; $bar_bound*$n_cur/$total")
# 写法倒是酷, 可惜无法兼容 symb='-'
# printf -v bar "$symb%.0s" $(seq $bar_len)
bar=$(repeat_str "$symb" $bar_len)
printf -v desc "%.2f%%|%d/%d" $p_cur $n_cur $total
#log_info "count:<$n_cur/$total>; p_cur:<$p_cur> bar:<$bar_len/$bar_bound>; desc=<$desc>; desc_len=<$desc_len>"
# 如果不考虑进度在增长过程中的减少 -------------
#printf "\e[s\e[${p_clrs}m\e[$row;${bar_col}H%.${bar_bound}s\e[0m\e[${t_clrs}m\e[${row};${desc_col}H%${neg}${desc_len}s\e[0m\e[u" "$bar" "$desc"
# 否则 --------------
printf "\e[s\e[$row;${col}H%${width}s\e[${p_clrs}m\e[$row;${bar_col}H%.${bar_bound}s\e[0m\e[${t_clrs}m\e[${row};${desc_col}H%${neg}${desc_len}s\e[0m\e[u" '' "$bar" "$desc"
sleep $delay
done
# 根据指定, 保证打印 100%, 以及光标
if [ "$force" != no ]; then
bar=$(repeat_str "$symb" $bar_bound)
desc="100.00%|$total/$total"
printf "\e[s\e[${p_clrs}m\e[$row;${bar_col}H%.${bar_bound}s\e[0m\e[${t_clrs}m\e[${row};${desc_col}H%${neg}${desc_len}s\e[0m\e[u" "$bar" "$desc"
printf "\e[?25h"
sleep 1
fi
fi
else
log_error "One rate-progress with inavailable width <$width> found. Position:<$row row, $col col> ."
fi
}
# 查询参数
eval $PASX
local style assign_fun delay p_clrs step row col width symbols erase total t_clrs use_op align force
local ending max_w max_col
local sh=$(tput lines) sw=$(tput cols)
params " 'style y' overall" \
" 'assign-fun m' " \
" 'delay d' $PB_DELAY " \
" 'p-clrs c' $(random_color) " \
" 'step p' $PB_STEP " \
" 'row r' $sh " \
" 'col l' 1 " \
" 'width w' " \
" 'symbols s'$(random_symbol) " \
" 'erase e' yes no " \
" 'total t' $PB_TOTAL " \
" 't-clrs x' $(random_color)" \
" 'use-op o' yes no" \
" 'align a' left " \
" 'force f' yes no"
# 初始化, 并修正部分参数
_pb_init
# 百分比进度条自身可以结束, 往返进度条是死循环需要中断, 但内部均统一为可中断
trap "pb_clean_spot" SIGTERM SIGINT SIGKILL
case $style in
step)
pb_step ;;
overall)
pb_overall ;;
rate)
pb_rate ;;
*)
log_error "Unimplemented progress bar style found."
return 1
esac
# 如果需要清除残余进度
[ "$erase" != no ] && printf "\e[s\e[$row;${col}H%${width}s\e[u"
# 卸载函数
unset -f _pb_init pb_step pb_overall pb_rate
}
【旋转字符提示】
位于 progress_bar_v2.sh
:<<COMMENT
在指定位置, 循环依次打印给定字符串中的一个字符
-c: --clrs 颜色字符串, 默认随机颜色, 即沿用默认颜色
-s: --symbols 要轮流打印的字符组成的字符串, 默认值 PB_TURNING
-i: --interval 字符间间隔时限, 单位秒, 默认值 PB_DELAY
-w: --width 占列数, 默认为 R_SYMB_W
-r: --row 打印位置之第几行( 1 起始), 默认值 SCREEN_BOTTOM_LINE
-l: --col 打印位置之第列行( 1 起始), 默认值为紧靠右侧, 即倒数第 width 列, 或者说第 SCREEN_WIDTH-width+1
-e, --erase 终止前, 是否清除残余显示. 这是个标记参数, 无标记或显式标记为 no, 则不清除; 否则(包括设置空值)表示清除
用法: vary_symbol -c <clrs='0'> -s <symbols=PB_TURNING> -d <delay=PB_DELAY> -r <row=SCREEN_BOTTOM_LINE> -l <col=PB_RIGHT_COL> -w <width=2>
注意:
1. 无限循环, 需要父函数 kill 才能退出
2. 为了表达简单清晰, 用法中未提及对应的长选项参数.
COMMENT
vary_symbol(){
trap "pb_clean_spot" SIGTERM SIGINT SIGKILL
#trap "$RESET_SPOT" SIGTERM SIGINT SIGKILL
local sh=$(tput lines) sw=$(tput cols)
eval $PASX
local clrs symbols interval width row col erase
# col 需要加 1 才指向目标列
params " 'clrs c' $(random_color) " \
" 'symbols s' $PB_TURNING" \
" 'interval i' $PB_DELAY" \
" 'width w' $R_SYMB_W" \
" 'row r' $sh" \
" 'col l' $[sw-width+1]" \
" 'erase e' yes no"
# 修正位置参数, 不出界
limit_bound row 1 $sh
limit_bound col 1 $sw
if [ $[col+width-1] -le $sw ]; then
local len=${#symbols} ending
while : ; do
for((i=0;i<len;i++));do
[ "$ending" ] && break 2
printf "\e[s\e[$row;${col}H\e[${clrs}m%${width}s\e[0m\e[u" "${symbols:i:1}" # 符号右对齐
sleep $interval
done
done
[ "$erase" != no ] && printf "\e[s\e[$row;${col}H%${width}s\e[u"
else
log_error "One rotaing symbol with inavailable width <$width> found. Position:<$row row, $col col> ."
fi
}
【时间消耗展示】
:<<COMMENT
在指定位置, 循环打印从函数被调用时开始的耗用时间. 所有命名参数, 短参数优先级高于长参数
-c, --clrs 颜色字符串, 默认随机颜色
-i, --interval 计时间隔, 默认值 PB_DELAY
-r, --row 打印位置之第几行( 1 起始), 默认值 SCREEN_BOTTOM_LINE
-l, --col 打印位置之第列行( 1 起始), 默认值 1
-w, --width 占列数, 默认L_TIME_W
-e, --erase 终止前, 是否清除残余显示. 这是个标记参数, 无标记或显式标记为 no, 则不清除; 否则(包括设置空值)表示清除
用法: elapsing_time -c <clrs='0'> -i <interval=PB_DELAY> -r <row=SCREEN_BOTTOM_LINE> -l <col=SCREEN_LEFT_COL> -w <width=10>
注意:
1. 无限循环, 需要父函数 kill 才能退出
2. 为了表达简单清晰, 用法中未提及对应的长选项参数.
COMMENT
elapsing_time(){
trap "pb_clean_spot" SIGTERM SIGINT SIGKILL
#trap "$RESET_SPOT" SIGTERM SIGINT SIGKILL
local sh=$(tput lines) sw=$(tput cols)
local clrs interval row col width erase
eval $PASX
params " 'clrs c' $(random_color) " \
" 'interval i' $PB_DELAY " \
" 'row r' $sh" \
" 'col l' 1" \
" 'width w' $L_TIME_W" \
" 'erase e' yes no"
# 修正位置参数, 不出界
limit_bound row 1 $sh
limit_bound col 1 $sw
if [ $[col+width-1] -le $sw ]; then
local start=$(start_watch) end ending str
while [ -z "$ending" ] ; do
end=$(stop_watch $start 1)
printf -v str "%.1fs" $end
printf "\e[s\e[$row;${col}H\e[${clrs}m%-${width}.${width}s\e[0m\e[u" "$str" # 时间左对齐
sleep $interval
done
[ "$erase" != no ] && printf "\e[s\e[$row;${col}H%${width}s\e[u"
else
log_error "One elapsing timer with inavailable width <$width> found. Position:<$row row, $col col> ."
fi
}
【测试】
新建文件 test.sh. 测试文件比较长, 但并不复杂, 展示了各种需求下的进度条使用范例.
local total_cols=$(tput cols) v
local total_rows=$(tput lines)
# 打印标尺占满一行
_ruler(){
for i in $(seq 1 $total_cols);do
v=$[i%10]
printf "%s" $v
done
echo
}
_assign(){
local v_name=$1 incr
sleep 0.2
incr=$(random_from_range 5 10)
eval "let $v_name+=$incr"
}
# 带任意参数, 则模拟打印输出
_process_main(){
for((i=1;i<$[total_rows-5];i++));do
sleep 0.8
[ "$@" ] && info -e "\t step $i completed\t$(random_string $[total_cols-35])"
done
}
way1(){
## 往返进度条(步进型和整体型)
local row col w delay style era
for row in $(seq 5 2 $[total_rows-5] ); do
for col in $(seq 1 40 $total_cols); do
delay=$(random_from_strings 0.1 0.3 0.5 0.7 0.9 1.2)
w=$(random_from_range 20 38)
style=$(random_from_strings step overall)
era=$(random_from_strings '' -e)
progress_bar -d $delay -r $row -l $col -w $w -y $style $era &
push $!
#sleep 2
done
done
_process_main y
kill ${__Stacking__[@]} 2>/dev/null || : # 栈数据有些不是后台进程 id
# local idx count=10
# for idx in $(seq 1 55); do
# progress_bar -d 0.3 -r 15 -l 5 -w 50 -y step ---erase &
# push $!
# progress_bar -d 0.3 -r 18 -l 5 -w 50 -y -e &
# push $!
# progress_bar -d 0.3 -r 21 -l 5 -w 50 -y step ---erase &
# push $!
# sleep 0.1
# done
# _process_main y
# kill ${__Stacking__[@]} 2>/dev/null || :
# sleep 0.5
}
way2(){
# 百分比进度条
local row col w delay style align era force
for row in $(seq 5 2 $[total_rows-5] ); do
for col in $(seq 5 30 $total_cols); do
delay=$(random_from_strings 0.1 0.3 0.5 0.7 0.9 1.2)
w=$(random_from_range 15 25)
style=$(random_from_strings step overall)
align=$(random_from_strings left right center)
era=$(random_from_strings '' -e)
force=$(random_from_strings '' -f)
progress_bar -d $delay -r $row -l $col -y rate -w $w -m _assign -t 180 --use-op -a $align $era $force &
push $!
done
done
_process_main y
# wait , kill 均可
wait ${__Stacking__[@]} 2>/dev/null || : # 栈数据可能有些不是后台进程 id
# progress_bar -d 0.1 -r 15 -l 3 -y rate -w 98 -m _assign -t 180 -o y -a right -f &
# sleep 3
# kill $!
}
way3(){
# 极简使用步进型往返进度条
# set -x
progress_bar -y step --delay 0.01 &
# set +x
_process_main y
kill $!
}
way4(){
# 极简使用整体型往返进度条
progress_bar -delay 0.07 &
_process_main y
kill $!
}
way5(){
# 极简使用百分比进度条
progress_bar -r $[total_rows-4] -t 366 -m _assign -o y -style rate &
progress_bar -r $[total_rows-2] -t 408 -m _assign -o y -a right --y rate &
progress_bar -r $[total_rows] -t 213 -m _assign -o y -a center -e y -y rate &
_process_main y
wait
}
way6(){
# 变换符号
local row col w delay symbols era
for row in $(seq 5 2 $[total_rows-5]); do
for col in $(seq -1 20 $total_cols); do
delay=$(random_from_strings 0.1 0.3 0.5 0.7 0.9 1.2)
w=$(random_from_range 1 16)
symbols=$(random_from_strings abcd 1234 '&$@' '$%^&(' 'mygod' 'good heaven')
era=$(random_from_strings '' -e)
vary_symbol -i $delay -row $row --col $col --width $w -s $symbols $era &
push $!
done
done
_process_main y
kill ${__Stacking__[@]} 2>/dev/null || : # 栈数据有些不是后台进程 id
}
way7(){
# 耗用时间
local row col w delay era
for row in $(seq 5 2 $[total_rows-5]); do
for col in $(seq 1 15 $total_cols); do
delay=$(random_from_strings 0.1 0.3 0.5 0.7 0.9 1.2)
w=$(random_from_range 5 13)
era=$(random_from_strings '' -e)
elapsing_time -i $delay -r $row -------l $col -width $w $era &
push $!
done
done
_process_main y
kill ${__Stacking__[@]} 2>/dev/null || : # 栈数据有些不是后台进程 id
# elapsing_time - -row 20 -col 10 -width 12 -i 1 &
# sleep 2
# kill $!
# sleep 0.5
}
way8(){
# 旋转符号和耗用时间的极简使用
vary_symbol &
local id_roll=$!
elapsing_time &
_process_main y
kill $id_roll $!
}
way9(){
# 百分比进度条结合旋转符号和耗用时间的极简使用
progress_bar -t 439 -m _assign -o y -x "33" -l $[L_TIME_W+1] -w $[total_cols-R_SYMB_W-1] -a center -style rate -e &
local bar_id=$!
vary_symbol -c "31;5" -e &
local roll_id=$!
elapsing_time -c "32" ----erase &
_process_main y
kill $roll_id $! $bar_id 2>/dev/null || :
}
# start testing
local fun title titles=(
"往返进度条(步进型和整体型)" # way1
"百分比进度条" # way2
"极简使用步进型往返进度条" # ...
"极简使用整体型往返进度条"
"极简使用百分比进度条"
"旋转字符"
"耗用时间(被 kill 时, 随机清除现场)"
"旋转符号和耗用时间的极简使用"
"百分比进度条结合旋转符号和耗用时间的极简使用(指定百分数, 旋转符号和耗用时间)" # way9
)
for idx in ${!titles[@]}; do
title=${titles[$idx]}
menu -n "回车(或者 3 秒后), 进入$title: "
read -t 3 || :
erase_screen
_ruler
aop caption right "$[idx+1]. ${title}展示..."
fun="way$[idx+1]"
$fun
unset -f $fun
done
运行结果如下: