九 综合进度条

九 综合进度条

【动机】

经常在线安装软件包的朋友, 一定对下载/安装时的进度条印象深刻. 本节起, 一共使用四节的篇幅, 与大家分享几种进度条的制作与使用.

【预备常量和函数】

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

运行结果如下: