【问题标题】:How to add a progress bar to a shell script?如何在 shell 脚本中添加进度条?
【发布时间】:2010-09-19 06:43:00
【问题描述】:

在 bash 或 *NIX 中的任何其他 shell 中编写脚本时,在运行需要几秒钟以上的命令时,需要一个进度条。

例如,复制一个大文件,打开一个大 tar 文件。

您推荐哪些方式向 shell 脚本添加进度条?

【问题讨论】:

  • 参见stackoverflow.com/questions/12498304/… 以了解控制逻辑的示例(后台作业并做某事直到完成)。
  • 在编写脚本时,我们经常发现一组要求很有用。记录、显示进度、颜色、精美的输出等等……我一直觉得应该有某种简单的脚本框架。最后我决定实施一个,因为我找不到任何东西。您可能会发现这很有帮助。它是纯 bash,我的意思是 Just Bash。 github.com/SumuduLansakara/JustBash
  • 不应该转移到unix.stackexchange.com吗?
  • 我喜欢将pv 用于任何可以通过管道传输的内容。示例:ssh remote "cd /home/user/ && tar czf - accounts" | pv -s 23091k | tar xz

标签: bash shell zsh


【解决方案1】:

您可以通过覆盖一行来实现这一点。使用\r 回到行首,无需将\n 写入终端。

完成后写\n 以推进线路。

使用echo -ne 来:

  1. 不打印\n
  2. 识别转义序列,如\r

这是一个演示:

echo -ne '#####                     (33%)\r'
sleep 1
echo -ne '#############             (66%)\r'
sleep 1
echo -ne '#######################   (100%)\r'
echo -ne '\n'

在下面的评论中,puk 提到如果您从长行开始然后想写短行,则此“失败”:在这种情况下,您需要覆盖长行的长度(例如,使用空格)。

【讨论】:

  • 根据 echo 手册页(至少在 MacOS X 上)sh/bash 使用他们自己的内置 echo 命令接受 "-n" ... 所以为了完成同样的事情,您需要将 \r\c 放在字符串的末尾,而不仅仅是 \r
  • 输出它的便携方式是使用printf而不是echo
  • 对于 printf,我们必须使用这种格式:printf "#### (50%%)\r",它不适用于单引号并且需要转义百分号。
  • 我不明白这一点 - 接受并为“我猜这个操作将在未知硬件上花费多长时间”hack 获得大量支持? pv 是 IMO 的正确答案(但 bar 也可以)
  • 问题是“我如何做进度条”,并以复制文件为例。我关注的是“图形”问题,而不是计算文件复制操作的距离。
【解决方案2】:

您可能也对how to do a spinner感兴趣:

我可以在 Bash 中做一个微调器吗?

当然!

i=1
sp="/-\|"
echo -n ' '
while true
do
    printf "\b${sp:i++%${#sp}:1}"
done

每次循环迭代时,它都会显示 sp 中的下一个字符 字符串,当它到达末端时环绕。 (我是位置 要显示的当前字符,${#sp} 是 sp 的长度 字符串)。

\b 字符串替换为“退格”字符。或者, 您可以使用 \r 回到行首。

如果你想让它慢下来,在循环里面放一个 sleep 命令 (在 printf 之后)。

POSIX 等价物是:

sp='/-\|'
printf ' '
while true; do
    printf '\b%.1s' "$sp"
    sp=${sp#?}${sp%???}
done

如果您已经有一个执行大量工作的循环,您可以调用 在每次迭代开始时使用以下函数来更新 微调器:

sp="/-\|"
sc=0
spin() {
   printf "\b${sp:sc++:1}"
   ((sc==${#sp})) && sc=0
}
endspin() {
   printf "\r%s\n" "$@"
}

until work_done; do
   spin
   some_work ...
done
endspin

【讨论】:

  • 更短的版本,完全可移植*:while :;do for s in / - \\ \|; do printf "\r$s";sleep .1;done;done(*:sleep 可能需要整数而不是小数)
  • @Daenyth。谢谢。请问我们应该在哪里调用我们需要观察它的命令是使用以前的代码进行的?
  • @goro:在上面的some_work ... 行中;可以在here 找到基于此有用答案和 Adam Katz 的有用评论的更详细讨论 - 重点是 POSIX 合规性。
  • @AdamKatz:这是一个有用的、可移植的简化,但为了匹配 Daenyth 的方法,微调器必须基于 \b 而不是 \r,否则它只会在一开始就起作用一行:while :; do for c in / - \\ \|; do printf '%s\b' "$c"; sleep 1; done; done - 或者,如果不希望在微调器后面显示光标:printf ' ' && while :; do for c in / - \\ \|; do printf '\b%s' "$c"; sleep 1; done; done
  • @kaushal - Ctrl+C 将手动停止它。如果您有后台作业,可以存储其 PID (job=$!),然后运行 ​​while kill -0 $job 2>/dev/null;do …,例如:sleep 15 & job=$!; while kill -0 $job 2>/dev/null; do for s in / - \\ \|; do printf "\r$s"; sleep .1; done; done
【解决方案3】:

得到了我前几天写的一个简单的进度条功能:

#!/bin/bash
# 1. Create ProgressBar function
# 1.1 Input is currentState($1) and totalState($2)
function ProgressBar {
# Process data
    let _progress=(${1}*100/${2}*100)/100
    let _done=(${_progress}*4)/10
    let _left=40-$_done
# Build progressbar string lengths
    _fill=$(printf "%${_done}s")
    _empty=$(printf "%${_left}s")

# 1.2 Build progressbar strings and print the ProgressBar line
# 1.2.1 Output example:                           
# 1.2.1.1 Progress : [########################################] 100%
printf "\rProgress : [${_fill// /#}${_empty// /-}] ${_progress}%%"

}

# Variables
_start=1

# This accounts as the "totalState" variable for the ProgressBar function
_end=100

# Proof of concept
for number in $(seq ${_start} ${_end})
do
    sleep 0.1
    ProgressBar ${number} ${_end}
done
printf '\nFinished!\n'

或者从,
https://github.com/fearside/ProgressBar/

【讨论】:

  • 你能解释一下1.2.1.1下面的那行吗?您是否使用 _fill 和 _empty 变量执行 sed 替换?我很困惑。
  • 我没有使用 sed,而是使用 bash 内部的“子字符串替换”,因为这是一个简单的工作,我更喜欢使用 bash 的内部函数来完成这种工作。代码看起来也更好。 :-) 在这里查看tldp.org/LDP/abs/html/string-manipulation.html 并搜索子字符串替换。
  • 和 ${_fill} 被分配为 ${_done} 个空格。这很漂亮。伟大的工作人。我肯定会在我所有的 bash 脚本中使用它哈哈
  • 干得好@fearside!当 _progress 没有从最后一个值改变时,我做了一些调整以跳过,以提高速度。 github.com/enobufs/bash-tools/blob/master/bin/progbar
  • 甜蜜。通过矩形更改破折号使其具有更专业的外观和感觉:printf "\rProgress : [${_fill// /▇}${_empty// / }] ${_progress}%%"
【解决方案4】:

使用 Linux 命令pv

如果它位于管道的中间,它不知道大小,但它给出了速度和总量,从那里你可以计算出需要多长时间并获得反馈,这样你就知道它没有挂起.

【讨论】:

    【解决方案5】:

    一些帖子显示了如何显示命令的进度。为了计算它,你需要看看你进步了多少。在 BSD 系统上,一些命令,例如 dd(1),接受SIGINFO 信号,并报告它们的进度。在 Linux 系统上,某些命令会以类似方式响应 SIGUSR1。如果此工具可用,您可以通过dd 管道输入以监控处理的字节数。

    或者,您可以使用lsof 获取文件读取指针的偏移量,从而计算进度。我编写了一个名为pmonitor 的命令,它显示处理指定进程或文件的进度。有了它,你可以做一些事情,比如以下。

    $ pmonitor -c gzip
    /home/dds/data/mysql-2015-04-01.sql.gz 58.06%
    

    早期版本的 Linux 和 FreeBSD shell 脚本出现在 my blog(“监控 Unix 上的进程进度”)上。

    【讨论】:

    • 这太棒了,我总是忘记通过 pv 进行管道传输 :-) 我认为我的“stat”命令的工作方式有点不同,我的 (Linux) 版本的这个脚本:gist.github.com/unhammer/b0ab6a6aa8e1eeaf236b
    • 请按照此帮助页面的要求在您的答案中引用代码的相关部分:stackoverflow.com/help/how-to-answer
    • @cpm 我引用了链接的标题。如果您认为还需要其他内容,请更具体。
    【解决方案6】:

    我正在寻找比所选答案更性感的东西,我自己的脚本也是如此。

    预览

    来源

    我把它放在github progress-bar.sh

    progress-bar() {
      local duration=${1}
    
    
        already_done() { for ((done=0; done<$elapsed; done++)); do printf "▇"; done }
        remaining() { for ((remain=$elapsed; remain<$duration; remain++)); do printf " "; done }
        percentage() { printf "| %s%%" $(( (($elapsed)*100)/($duration)*100/100 )); }
        clean_line() { printf "\r"; }
    
      for (( elapsed=1; elapsed<=$duration; elapsed++ )); do
          already_done; remaining; percentage
          sleep 1
          clean_line
      done
      clean_line
    }
    

    用法

     progress-bar 100
    

    【讨论】:

    • 我不明白它是如何集成到一些不知道过程长度的处理中的。如果我的过程较早完成,如何停止进度条,例如用于解压缩文件。
    • 我觉得用法应该是progress-bar 100
    • @faceless 你提供的时间不在这段代码的范围内,它倒计时
    • @Fusion 它是一个 unicode 字符(U+2587 LOWER EIGHTHS BLOCK),对于现代 shell 来说应该是安全的。试试你的环境
    • @RajeshHatwar 你不能没有杂技。它只是漂亮的计时器,而不是进度条。
    【解决方案7】:

    还没有看到任何类似的东西,这里的所有自定义函数似乎都只专注于渲染,所以...下面是我非常简单的符合 POSIX 标准的解决方案,并附有分步说明,因为这个问题并非微不足道。

    TL;DR

    渲染进度条非常简单。估计应该渲染多少是另一回事。这是渲染(动画)进度条的方法 - 您可以将此示例复制并粘贴到文件中并运行它:

    #!/bin/sh
    
    BAR='####################'   # this is full bar, e.g. 20 chars
    
    for i in {1..20}; do
        echo -ne "\r${BAR:0:$i}" # print $i chars of $BAR from 0 position
        sleep .1                 # wait 100ms between "frames"
    done
    
    • {1..20} - 值从 1 到 20
    • echo - 打印到终端(即到stdout
    • echo -n - 打印末尾不换行
    • echo -e - 打印时解释特殊字符
    • "\r" - 回车,返回行首的特殊字符

    您可以让它以任何速度呈现任何内容,因此这种方法非常通用,例如常用于可视化愚蠢电影中的“黑客行为”,不开玩笑。

    完整答案

    问题的核心是如何确定$i 值,即显示多少进度条。在上面的示例中,我只是让它在for 循环中递增以说明原理,但现实生活中的应用程序将使用无限循环并在每次迭代时计算$i 变量。要进行上述计算,它需要以下成分:

    1. 还有多少工作要做
    2. 到目前为止已经完成了多少工作

    如果是cp,它需要源文件的大小和目标文件的大小:

    #!/bin/sh
    
    src="/path/to/source/file"
    tgt="/path/to/target/file"
    
    cp "$src" "$tgt" &                     # the & forks the `cp` process so the rest
                                           # of the code runs without waiting (async)
    
    BAR='####################'
    
    src_size=$(stat -c%s "$src")           # how much there is to do
    
    while true; do
        tgt_size=$(stat -c%s "$tgt")       # how much has been done so far
        i=$(( $tgt_size * 20 / $src_size ))
        echo -ne "\r${BAR:0:$i}"
        if [ $tgt_size == $src_size ]; then
            echo ""                        # add a new line at the end
            break;                         # break the loop
        fi
        sleep .1
    done
    
    • foo=$(bar) - 在子进程中运行 bar 并将其 stdout 保存到 $foo
    • stat - 将文件统计信息打印到 stdout
    • stat -c - 打印一个格式化的值
    • %s - 总大小的格式

    在文件解包等操作的情况下,计算源大小会稍微困难一些,但仍然像获取未压缩文件的大小一样容易:

    #!/bin/sh
    src_size=$(gzip -l "$src" | tail -n1 | tr -s ' ' | cut -d' ' -f3)
    
    • gzip -l - 打印有关 zip 存档的信息
    • tail -n1 - 从底部开始使用 1 行
    • tr -s ' ' - 将多个空格转换为一个(“挤压”它们)
    • cut -d' ' -f3 - 剪切第三个空格分隔的字段(列)

    这就是我之前提到的问题的实质。这种解决方案越来越不通用。实际进度的所有计算都与您尝试可视化的域紧密相关,它是单个文件操作、计时器倒计时、目录中文件数量的增加、对多个文件的操作等等,因此,它不能重复使用。唯一可重用的部分是进度条渲染。要重用它,您需要将其抽象并保存在文件中(例如/usr/lib/progress_bar.sh),然后定义计算特定于您的域的输入值的函数。这就是通用代码的样子(我还使 $BAR 动态化,因为人们要求它,其余的现在应该很清楚了):

    #!/bin/sh
    
    BAR_length=50
    BAR_character='#'
    BAR=$(printf %${BAR_length}s | tr ' ' $BAR_character)
    
    work_todo=$(get_work_todo)             # how much there is to do
    
    while true; do
        work_done=$(get_work_done)         # how much has been done so far
        i=$(( $work_done * $BAR_length / $work_todo ))
        echo -ne "\r${BAR:0:$i}"
        if [ $work_done == $work_todo ]; then
            echo ""
            break;
        fi
        sleep .1
    done
    
    • printf - 用于以给定格式打印内容的内置函数
    • printf %50s - 只打印 50 个空格
    • tr ' ' '#' - 将每个空格转换为井号

    这就是你的使用方式:

    #!/bin/sh
    
    src="/path/to/source/file"
    tgt="/path/to/target/file"
    
    function get_work_todo() {
        echo $(stat -c%s "$src")
    }
    
    function get_work_done() {
        [ -e "$tgt" ] &&                   # if target file exists
            echo $(stat -c%s "$tgt") ||    # echo its size, else
            echo 0                         # echo zero
    }
    
    cp "$src" "$tgt" &                     # copy in the background
    
    source /usr/lib/progress_bar.sh        # execute the progress bar
    

    显然,您可以将其包装在一个函数中,重写以使用管道流,使用 $! 获取分叉的进程 ID 并将其传递给 progress_bar.sh,以便它可以猜测如何计算工作做和做,不管你的毒是什么。

    旁注

    在与同行讨论这个答案时,您可能会发现最常出现的两个主题:

    1. ${}:在上面的例子中我使用${foo:A:B}。这种语法的技术术语是 Parameter Expansion,一个内置的 shell 功能,允许操作变量(参数),例如用: 修剪字符串,但也可以做其他事情——它不会产生子shell。我能想到的参数扩展最突出的描述(不完全兼容 POSIX,但可以让读者很好地理解这个概念)在 man bash 页面中。
    2. $():在上面的例子中我使用foo=$(bar)。它在子进程(也称为 Subshel​​l)中生成一个单独的 shell,在其中运行 bar 命令并将其标准输出分配给 $foo 变量。它与 Process Substitution 不同,它与 pipe (|) 完全不同。最重要的是,它有效。有人说这应该避免,因为它很慢。我认为这在这里“没问题”,因为无论这段代码试图可视化的时间都足够长,以至于需要一个进度条。换句话说,子壳不是瓶颈。调用子 shell 也让我省去了解释为什么 return 不是大多数人认为的那样,什么是 Exit Status 以及为什么从 shell 中的函数传递值不是 shell 函数的原因擅长一般。要了解更多信息,我再次强烈推荐 man bash 页面。

    【讨论】:

    • 对于那些想要最简单的东西的人,我刚刚用 cprn 第一个答案制作了我的东西。这是一个非常简单的函数中的进度条,它使用一些愚蠢的比例规则来绘制进度条:pastebin.com/9imhRLYX
    • 使用bash而不是sh是正确的,否则有些人可以在${BAR:0:$i}上拥有Bad substitution
    • 你可能是对的。如今,许多发行版中的sh 都链接到bash 或运行bash --posix 兼容模式的脚本,我怀疑2016 年我编写和测试这个答案时我的系统上就是这样。如果它对您不起作用,您可以将${name:n:l} 替换为$(expr "x$name" : "x.\{0,$n\}\(.\{0,$l\}\)"),这已被证明可以在任何POSIX shell 中工作(起源于ksh93,也存在于zshmkshbusyboxsh) .不过,为了便于阅读,并且因为它应该只适用于绝大多数情况,我将保留原来的答案。
    【解决方案8】:

    GNU tar 有一个有用的选项,它提供了一个简单的进度条功能。

    (...) 另一个可用的检查点操作是“点”(或“.”)。它指示 tar 在标准列表流上打印一个点,例如:

    $ tar -c --checkpoint=1000 --checkpoint-action=dot /var
    ...
    

    可以通过以下方式获得相同的效果:

    $ tar -c --checkpoint=.1000 /var
    

    【讨论】:

    • +1 是最简单的方法!如果您没有看到打印的点,请尝试减少数字,例如--checkpoint=.10。使用tar -xz 提取时效果也很好。
    【解决方案9】:

    我也想贡献我的own progress bar

    通过Half unicode blocks实现子字符精度

    包含代码

    【讨论】:

    • 您可以使用另一组 UTF-8 字符来代替 halfblocks"▏","▎","▍","▌","▋","▊","▉"
    【解决方案10】:

    APT 风格的进度条(不破坏正常输出)

    编辑:有关更新版本,请查看我的 github page

    我对这个问题的回答不满意。我个人一直在寻找的是 APT 所看到的精美进度条。

    我查看了 APT 的 C 源代码,并决定为 bash 编写自己的等效代码。

    此进度条将很好地留在终端底部,不会干扰发送到终端的任何输出。

    请注意,该栏目前固定为 100 个字符宽。如果你想把它缩放到终端的大小,这也很容易完成(我的github页面上的更新版本很好地处理了这个)。

    我将在这里发布我的脚本。 使用示例:

    source ./progress_bar.sh
    echo "This is some output"
    setup_scroll_area
    sleep 1
    echo "This is some output 2"
    draw_progress_bar 10
    sleep 1
    echo "This is some output 3"
    draw_progress_bar 50
    sleep 1
    echo "This is some output 4"
    draw_progress_bar 90
    sleep 1
    echo "This is some output 5"
    destroy_scroll_area
    

    脚本(我强烈推荐我的 github 上的版本):

    #!/bin/bash
    
    # This code was inspired by the open source C code of the APT progress bar
    # http://bazaar.launchpad.net/~ubuntu-branches/ubuntu/trusty/apt/trusty/view/head:/apt-pkg/install-progress.cc#L233
    
    #
    # Usage:
    # Source this script
    # setup_scroll_area
    # draw_progress_bar 10
    # draw_progress_bar 90
    # destroy_scroll_area
    #
    
    
    CODE_SAVE_CURSOR="\033[s"
    CODE_RESTORE_CURSOR="\033[u"
    CODE_CURSOR_IN_SCROLL_AREA="\033[1A"
    COLOR_FG="\e[30m"
    COLOR_BG="\e[42m"
    RESTORE_FG="\e[39m"
    RESTORE_BG="\e[49m"
    
    function setup_scroll_area() {
        lines=$(tput lines)
        let lines=$lines-1
        # Scroll down a bit to avoid visual glitch when the screen area shrinks by one row
        echo -en "\n"
    
        # Save cursor
        echo -en "$CODE_SAVE_CURSOR"
        # Set scroll region (this will place the cursor in the top left)
        echo -en "\033[0;${lines}r"
    
        # Restore cursor but ensure its inside the scrolling area
        echo -en "$CODE_RESTORE_CURSOR"
        echo -en "$CODE_CURSOR_IN_SCROLL_AREA"
    
        # Start empty progress bar
        draw_progress_bar 0
    }
    
    function destroy_scroll_area() {
        lines=$(tput lines)
        # Save cursor
        echo -en "$CODE_SAVE_CURSOR"
        # Set scroll region (this will place the cursor in the top left)
        echo -en "\033[0;${lines}r"
    
        # Restore cursor but ensure its inside the scrolling area
        echo -en "$CODE_RESTORE_CURSOR"
        echo -en "$CODE_CURSOR_IN_SCROLL_AREA"
    
        # We are done so clear the scroll bar
        clear_progress_bar
    
        # Scroll down a bit to avoid visual glitch when the screen area grows by one row
        echo -en "\n\n"
    }
    
    function draw_progress_bar() {
        percentage=$1
        lines=$(tput lines)
        let lines=$lines
        # Save cursor
        echo -en "$CODE_SAVE_CURSOR"
    
        # Move cursor position to last row
        echo -en "\033[${lines};0f"
    
        # Clear progress bar
        tput el
    
        # Draw progress bar
        print_bar_text $percentage
    
        # Restore cursor position
        echo -en "$CODE_RESTORE_CURSOR"
    }
    
    function clear_progress_bar() {
        lines=$(tput lines)
        let lines=$lines
        # Save cursor
        echo -en "$CODE_SAVE_CURSOR"
    
        # Move cursor position to last row
        echo -en "\033[${lines};0f"
    
        # clear progress bar
        tput el
    
        # Restore cursor position
        echo -en "$CODE_RESTORE_CURSOR"
    }
    
    function print_bar_text() {
        local percentage=$1
    
        # Prepare progress bar
        let remainder=100-$percentage
        progress_bar=$(echo -ne "["; echo -en "${COLOR_FG}${COLOR_BG}"; printf_new "#" $percentage; echo -en "${RESTORE_FG}${RESTORE_BG}"; printf_new "." $remainder; echo -ne "]");
    
        # Print progress bar
        if [ $1 -gt 99 ]
        then
            echo -ne "${progress_bar}"
        else
            echo -ne "${progress_bar}"
        fi
    }
    
    printf_new() {
        str=$1
        num=$2
        v=$(printf "%-${num}s" "$str")
        echo -ne "${v// /$str}"
    }
    

    【讨论】:

    • 完美!正是我想要的
    【解决方案11】:

    使用 pipeview ( pv ) 实用程序在我的系统上工作的更简单的方法。

    srcdir=$1
    outfile=$2
    
    
    tar -Ocf - $srcdir | pv -i 1 -w 50 -berps `du -bs $srcdir | awk '{print $1}'` | 7za a -si $outfile
    

    【讨论】:

      【解决方案12】:

      这让您可以看到命令仍在执行:

      while :;do echo -n .;sleep 1;done &
      trap "kill $!" EXIT  #Die with parent if we die prematurely
      tar zxf packages.tar.gz; # or any other command here
      kill $! && trap " " EXIT #Kill the loop and unset the trap or else the pid might get reassigned and we might end up killing a completely different process
      

      这将创建一个在后台执行并回显“.”的无限循环。每一秒。这将在 shell 中显示 .。运行tar 命令或您想要的任何命令。当该命令完成执行时,然后 kill 在后台运行的最后一个作业 - 这是 infinite while 循环

      【讨论】:

      • 在执行期间不能在后台启动另一个作业,并且可能会被杀死而不是进度循环?
      • 我认为你的想法是你会把它放在一个脚本中,所以这只会捕获那个脚本的退出。
      • 我喜欢这个命令,我在我的文件中使用它。我只是有点不安,因为我真的不明白它是如何工作的。第一行和第三行比较容易理解,但我还是不确定。我知道这是一个古老的答案,但是有没有办法让我得到一个针对编程新手的不同解释
      • 这是唯一正确的答案,其他人只是脚本 101 玩具进度条,没有任何意义,对真正的、一次性的、不可跟踪的(几乎所有)程序没有用处。谢谢。
      • @Centimane 您可以在将 while 循环作为背景后立即将 PID 存储在一个变量中(即pid=$!),并在稍后甚至在陷阱声明中使用该变量:
      【解决方案13】:

      这是它的外观

      上传文件

      [##################################################] 100% (137921 / 137921 bytes)
      

      等待作业完成

      [#########################                         ] 50% (15 / 30 seconds)
      

      实现它的简单函数

      您可以将其复制粘贴到您的脚本中。它不需要任何其他工作。

      PROGRESS_BAR_WIDTH=50  # progress bar length in characters
      
      draw_progress_bar() {
        # Arguments: current value, max value, unit of measurement (optional)
        local __value=$1
        local __max=$2
        local __unit=${3:-""}  # if unit is not supplied, do not display it
      
        # Calculate percentage
        if (( $__max < 1 )); then __max=1; fi  # anti zero division protection
        local __percentage=$(( 100 - ($__max*100 - $__value*100) / $__max ))
      
        # Rescale the bar according to the progress bar width
        local __num_bar=$(( $__percentage * $PROGRESS_BAR_WIDTH / 100 ))
      
        # Draw progress bar
        printf "["
        for b in $(seq 1 $__num_bar); do printf "#"; done
        for s in $(seq 1 $(( $PROGRESS_BAR_WIDTH - $__num_bar ))); do printf " "; done
        printf "] $__percentage%% ($__value / $__max $__unit)\r"
      }
      

      使用示例

      在这里,我们上传一个文件并在每次迭代时重绘进度条。实际执行什么工作并不重要,只要我们能得到 2 个值:最大值和当前值。

      在下面的示例中,最大值为file_size,当前值由某个函数提供,称为uploaded_bytes

      # Uploading a file
      file_size=137921
      
      while true; do
        # Get current value of uploaded bytes
        uploaded_bytes=$(some_function_that_reports_progress)
      
        # Draw a progress bar
        draw_progress_bar $uploaded_bytes $file_size "bytes"
      
        # Check if we reached 100%
        if [ $uploaded_bytes == $file_size ]; then break; fi
        sleep 1  # Wait before redrawing
      done
      # Go to the newline at the end of upload
      printf "\n"
      

      【讨论】:

      • 简洁明了的功能。非常感谢!
      • 这就是我要寻找的!非常感谢:)
      【解决方案14】:

      大多数 unix 命令不会为您提供可以执行此操作的那种直接反馈。 有些会在您可以使用的 stdout 或 stderr 上为您提供输出。

      对于 tar 之类的内容,您可以使用 -v 开关并将输出传送到一个程序,该程序会为它读取的每一行更新一个小动画。当 tar 写出它被解开的文件列表时,程序可以更新动画。要完成百分比,您必须知道文件数并计算行数。

      据我所知,cp 没有给出这种输出。要监视 cp 的进度,您必须监视源文件和目标文件并观察目标的大小。您可以使用stat (2) 系统调用编写一个小型c 程序来获取文件大小。这将读取源文件的大小,然后轮询目标文件并根据写入日期的文件大小更新完成百分比条。

      【讨论】:

        【解决方案15】:

        我需要一个进度条来遍历 csv 文件中的行。能够将 cprn 的代码改编成对我有用的东西:

        BAR='##############################'
        FILL='------------------------------'
        totalLines=$(wc -l $file | awk '{print $1}')  # num. lines in file
        barLen=30
        
        # --- iterate over lines in csv file ---
        count=0
        while IFS=, read -r _ col1 col2 col3; do
            # update progress bar
            count=$(($count + 1))
            percent=$((($count * 100 / $totalLines * 100) / 100))
            i=$(($percent * $barLen / 100))
            echo -ne "\r[${BAR:0:$i}${FILL:$i:barLen}] $count/$totalLines ($percent%)"
        
            # other stuff
            (...)
        done <$file
        

        看起来像这样:

        [##----------------------------] 17128/218210 (7%)
        

        【讨论】:

        • 感谢您的解决方案!按我的需要工作。
        【解决方案16】:

        雇用(浮点)进度条

        序言

        对不起,这个不是那么短的答案。

        对于不耐烦的人:请在 现在开始测试代码!(在中间),最后动画演示(在此底部。)

        这里的所有演示都使用read -t &lt;float seconds&gt; &amp;&amp; break 而不是sleep。所以所有循环都可以通过点击 Return 键很好地停止。

        简介

        又一个 Bash 进度条...

        由于这里已经有很多答案,我想添加一些关于性能精度.

        1。避免分叉!

        因为进度条是为了在其他进程正在运行时运行,所以这一定是一个不错进程...

        因此,在不需要时避免使用 forks。示例:而不是

        mysmiley=$(printf '%b' \\U1F60E)
        

        使用

        printf -v mysmiley '%b' \\U1F60E
        

        解释:当你运行var=$(command)时,你启动了一个新进程来执行command并将他的输出发送到变量$var em> 一旦终止。这是非常资源昂贵。请比较:

        TIMEFORMAT="%R"
        time for ((i=2500;i--;)){ mysmiley=$(printf '%b' \\U1F60E);}
        2.292
        time for ((i=2500;i--;)){ printf -v mysmiley '%b' \\U1F60E;}
        0.017
        bc -l <<<'2.292/.017'
        134.82352941176470588235
        

        在我的主机上,分配 $mysmiley 的相同工作(仅 2500 次),使用 fork 似乎比使用内置 printf -v 慢约 135 倍/更昂贵。

        然后

        echo $mysmiley 
        ?
        

        所以你的 function 必须不打印(或输出)任何东西。您的函数必须将他的答案归因于一个变量

        2。使用整数作为伪浮点数

        这是一个非常小而快速的函数,用于从整数计算百分比,使用整数并回答伪浮点数:

        percent(){
            local p=00$(($1*100000/$2))
            printf -v "$3" %.2f ${p::-3}.${p: -3}
        }
        

        用法:

        # percent <integer to compare> <reference integer> <variable name>
        percent 33333 50000 testvar
        printf '%8s%%\n' "$testvar"
           66.67%
        

        3。使用 UTF-8 租用控制台图形:▏ ▎ ▍ ▌ ▋ ▊ ▉ █

        要使用 bash 渲染这些字符,您可以:

        printf -v chars '\\U258%X ' {15..8}
        printf "$chars\\n"
        ▏ ▎ ▍ ▌ ▋ ▊ ▉ █ 
        

        那么我们必须使用 8x string with 作为 graphic width

        现在就做吧!

        这个函数被命名为percentBar,因为它会根据以百分比(浮动)提交的参数呈现一个条形:

        percentBar ()  { 
            local prct totlen=$((8*$2)) lastchar barstring blankstring;
            printf -v prct %.2f "$1"
            ((prct=10#${prct/.}*totlen/10000, prct%8)) &&
                printf -v lastchar '\\U258%X' $(( 16 - prct%8 )) ||
                    lastchar=''
            printf -v barstring '%*s' $((prct/8)) ''
            printf -v barstring '%b' "${barstring// /\\U2588}$lastchar"
            printf -v blankstring '%*s' $(((totlen-prct)/8)) ''
            printf -v "$3" '%s%s' "$barstring" "$blankstring"
        }
        

        用法:

        # percentBar <float percent> <int string width> <variable name>
        percentBar 42.42 $COLUMNS bar1
        echo "$bar1"
        █████████████████████████████████▉                                              
        

        为了显示小的差异:

        percentBar 42.24 $COLUMNS bar2
        printf "%s\n" "$bar1" "$bar2"
        █████████████████████████████████▉                                              
        █████████████████████████████████▊                                              
        

        有颜色

        由于渲染变量是固定宽度的字符串,所以使用颜色很容易:

        percentBar 72.1 24 bar
        printf 'Show this: \e[44;33;1m%s\e[0m at %s%%\n' "$bar" 72.1
        

        小动画:

        for i in {0..10000..33} 10000;do i=0$i
            printf -v p %0.2f ${i::-2}.${i: -2}
            percentBar $p $((COLUMNS-9)) bar
            printf '\r|%s|%6.2f%%' "$bar" $p
            read -srt .002 _ && break    # console sleep avoiding fork
        done
        
        |███████████████████████████████████████████████████████████████████████|100.00%
        
        clear; for i in {0..10000..33} 10000;do i=0$i
             printf -v p %0.2f ${i::-2}.${i: -2}
             percentBar $p $((COLUMNS-7)) bar
             printf '\r\e[47;30m%s\e[0m%6.2f%%' "$bar" $p
             read -srt .002 _ && break
        done
        

        最后的动画演示

        另一个展示不同尺寸和彩色输出的演示:

        printf '\n\n\n\n\n\n\n\n\e[8A\e7'&&for i in {0..9999..99} 10000;do 
            o=1 i=0$i;printf -v p %0.2f ${i::-2}.${i: -2}
            for l in 1 2 3 5 8 13 20 40 $((COLUMNS-7));do
                percentBar $p $l bar$((o++));done
            [ "$p" = "100.00" ] && read -rst .8 _;printf \\e8
            printf '%s\e[48;5;23;38;5;41m%s\e[0m%6.2f%%%b' 'In 1 char width: ' \
                "$bar1" $p ,\\n 'with 2 chars: ' "$bar2" $p ,\\n 'or 3 chars: ' \
                "$bar3" $p ,\\n 'in 5 characters: ' "$bar4" $p ,\\n 'in 8 chars: ' \
                "$bar5" $p .\\n 'There are 13 chars: ' "$bar6" $p ,\\n '20 chars: '\
                "$bar7" $p ,\\n 'then 40 chars' "$bar8" $p \
                ', or full width:\n' '' "$bar9" $p ''
            ((10#$i)) || read -st .5 _; read -st .1 _ && break
        done
        

        可以产生这样的东西:

        【讨论】:

          【解决方案17】:

          我的解决方案显示压缩包的百分比 目前正在解压缩和写入。我用这个 写出 2GB 根文件系统映像时。你真的 这些东西需要一个进度条。我所做的是使用 gzip --list 获取未压缩的总大小 压缩包。由此我计算出所需的阻塞因子 将文件分成 100 个部分。最后,我打印一个 每个块的检查点消息。对于 2GB 文件,此 一个块大约 10MB。如果那太大了,那么你可以 将 BLOCKING_FACTOR 除以 10 或 100,但它是 很难以百分比形式打印漂亮的输出。

          假设您使用的是 Bash,那么您可以使用 跟随shell函数

          untar_progress () 
          { 
            TARBALL=$1
            BLOCKING_FACTOR=$(gzip --list ${TARBALL} |
              perl -MPOSIX -ane '$.==2 && print ceil $F[1]/50688')
            tar --blocking-factor=${BLOCKING_FACTOR} --checkpoint=1 \
              --checkpoint-action='ttyout=Wrote %u%  \r' -zxf ${TARBALL}
          }
          

          【讨论】:

          • 不错的解决方案,但是当您想压缩目录时该怎么做?
          【解决方案18】:

          首先bar不是唯一的一个pipe进度表。另一个(可能更知名)是 pv(管道查看器)。

          其次 bar 和 pv 可以像这样使用:

          $ bar file1 | wc -l 
          $ pv file1 | wc -l
          

          甚至:

          $ tail -n 100 file1 | bar | wc -l
          $ tail -n 100 file1 | pv | wc -l
          

          如果您想在处理参数中给出的文件的命令中使用 bar 和 pv,这是一个有用的技巧,例如复制file1 file2,就是使用process substitution

          $ copy <(bar file1) file2
          $ copy <(pv file1) file2
          

          进程替换是一种 bash 魔术,它创建临时 fifo 管道文件 /dev/fd/ 并通过该管道从已运行的进程(括号内)连接标准输出,并且复制看到它就像一个普通文件一样(除了一个例外,它可以只能往前读)。

          更新:

          bar 命令本身也允许复制。人吧之后:

          bar --in-file /dev/rmt/1cbn --out-file \
               tape-restore.tar --size 2.4g --buffer-size 64k
          

          但在我看来,流程替换是更通用的方法。它使用 cp 程序本身。

          【讨论】:

            【解决方案19】:

            基于 Edouard Lopez 的工作,我创建了一个适合屏幕大小的进度条,无论它是什么。看看吧。

            它也发布在Git Hub

            #!/bin/bash
            #
            # Progress bar by Adriano Pinaffo
            # Available at https://github.com/adriano-pinaffo/progressbar.sh
            # Inspired on work by Edouard Lopez (https://github.com/edouard-lopez/progress-bar.sh)
            # Version 1.0
            # Date April, 28th 2017
            
            function error {
              echo "Usage: $0 [SECONDS]"
              case $1 in
                1) echo "Pass one argument only"
                exit 1
                ;;
                2) echo "Parameter must be a number"
                exit 2
                ;;
                *) echo "Unknown error"
                exit 999
              esac
            }
            
            [[ $# -ne 1 ]] && error 1
            [[ $1 =~ ^[0-9]+$ ]] || error 2
            
            duration=${1}
            barsize=$((`tput cols` - 7))
            unity=$(($barsize / $duration))
            increment=$(($barsize%$duration))
            skip=$(($duration/($duration-$increment)))
            curr_bar=0
            prev_bar=
            for (( elapsed=1; elapsed<=$duration; elapsed++ ))
            do
              # Elapsed
            prev_bar=$curr_bar
              let curr_bar+=$unity
              [[ $increment -eq 0 ]] || {  
                [[ $skip -eq 1 ]] &&
                  { [[ $(($elapsed%($duration/$increment))) -eq 0 ]] && let curr_bar++; } ||
                { [[ $(($elapsed%$skip)) -ne 0 ]] && let curr_bar++; }
              }
              [[ $elapsed -eq 1 && $increment -eq 1 && $skip -ne 1 ]] && let curr_bar++
              [[ $(($barsize-$curr_bar)) -eq 1 ]] && let curr_bar++
              [[ $curr_bar -lt $barsize ]] || curr_bar=$barsize
              for (( filled=0; filled<=$curr_bar; filled++ )); do
                printf "▇"
              done
            
              # Remaining
              for (( remain=$curr_bar; remain<$barsize; remain++ )); do
                printf " "
              done
            
              # Percentage
              printf "| %s%%" $(( ($elapsed*100)/$duration))
            
              # Return
              sleep 1
              printf "\r"
            done
            printf "\n"
            exit 0
            

            享受

            【讨论】:

              【解决方案20】:

              我更喜欢将 dialog--gauge 参数一起使用。经常用于 .deb 包安装和许多发行版的其他基本配置内容。所以你不需要重新发明轮子......再次

              只需输入一个从 1 到 100 @stdin 的 int 值。一个基本而愚蠢的例子:

              for a in {1..100}; do sleep .1s; echo $a| dialog --gauge "waiting" 7 30; done
              

              我有这个 /bin/Wait 文件(带有 chmod u+x perms)用于烹饪目的:P

              #!/bin/bash
              INIT=`/bin/date +%s`
              NOW=$INIT
              FUTURE=`/bin/date -d "$1" +%s`
              [ $FUTURE -a $FUTURE -eq $FUTURE ] || exit
              DIFF=`echo "$FUTURE - $INIT"|bc -l`
              
              while [ $INIT -le $FUTURE -a $NOW -lt $FUTURE ]; do
                  NOW=`/bin/date +%s`
                  STEP=`echo "$NOW - $INIT"|bc -l`
                  SLEFT=`echo "$FUTURE - $NOW"|bc -l`
                  MLEFT=`echo "scale=2;$SLEFT/60"|bc -l`
                  TEXT="$SLEFT seconds left ($MLEFT minutes)";
                  TITLE="Waiting $1: $2"
                  sleep 1s
                  PTG=`echo "scale=0;$STEP * 100 / $DIFF"|bc -l`
                  echo $PTG| dialog --title "$TITLE" --gauge "$TEXT" 7 72
              done
              
              if [ "$2" == "" ]; then msg="Espera terminada: $1";audio="Listo";
              else msg=$2;audio=$2;fi 
              
              /usr/bin/notify-send --icon=stock_appointment-reminder-excl "$msg"
              espeak -v spanish "$audio"
              

              所以我可以说:

              Wait "34 min" "warm up the oven"

              Wait "dec 31" "happy new year"

              【讨论】:

                【解决方案21】:

                许多答案都描述了编写自己的命令来打印出'\r' + $some_sort_of_progress_msg。有时问题是每秒打印出数百个这样的更新会减慢进程。

                但是,如果您的任何进程产生输出(例如,7z a -r newZipFile myFolder 将在压缩文件名时输出每个文件名),那么存在更简单、快速、轻松且可定制的解决方案。

                安装python模块tqdm

                $ sudo pip install tqdm
                $ # now have fun
                $ 7z a -r -bd newZipFile myFolder | tqdm >> /dev/null
                $ # if we know the expected total, we can have a bar!
                $ 7z a -r -bd newZipFile myFolder | grep -o Compressing | tqdm --total $(find myFolder -type f | wc -l) >> /dev/null
                

                帮助:tqdm -h。使用更多选项的示例:

                $ find / -name '*.py' -exec cat \{} \; | tqdm --unit loc --unit_scale True | wc -l
                

                作为奖励,您还可以使用 tqdm 在 python 代码中包装可迭代对象。

                https://github.com/tqdm/tqdm/blob/master/README.rst#module

                【讨论】:

                • 我认为您的“更多选项”示例不起作用。它似乎通过管道将tqdm STDOUT 传递给wc -l。你可能想逃避它。
                • @cprn tqdm 将在STDERR 上显示进度,同时将其输入STDIN 传送到STDOUT。在这种情况下,wc -l 会收到与不包含 tqdm 相同的输入。
                • 啊,现在说得通了。感谢您的解释。
                【解决方案22】:

                到目前为止,对我来说最容易使用和最好看的是命令 pvbar,就像某些人已经写的那样

                例如:需要用dd备份整个驱动器

                通常你使用dd if="$input_drive_path" of="$output_file_path"

                使用pv 你可以这样:

                dd if="$input_drive_path" | pv | dd of="$output_file_path"

                进度直接转到STDOUT,如下所示:

                    7.46GB 0:33:40 [3.78MB/s] [  <=>                                            ]
                

                完成后总结出来

                    15654912+0 records in
                    15654912+0 records out
                    8015314944 bytes (8.0 GB) copied, 2020.49 s, 4.0 MB/s
                

                【讨论】:

                • 您可以使用pvbar 来可视化不同进程的进度,例如计时器倒计时、文本文件中的位置、您的应用安装、运行时设置等?
                【解决方案23】:

                要指示活动的进度,请尝试以下命令:

                while true; do sleep 0.25 && echo -ne "\r\\" && sleep 0.25 && echo -ne "\r|" && sleep 0.25 && echo -ne "\r/" && sleep 0.25 && echo -ne "\r-"; done;
                

                while true; do sleep 0.25 && echo -ne "\rActivity: \\" && sleep 0.25 && echo -ne "\rActivity: |" && sleep 0.25 && echo -ne "\rActivity: /" && sleep 0.25 && echo -ne "\rActivity: -"; done;
                

                while true; do sleep 0.25 && echo -ne "\r" && sleep 0.25 && echo -ne "\r>" && sleep 0.25 && echo -ne "\r>>" && sleep 0.25 && echo -ne "\r>>>"; sleep 0.25 && echo -ne "\r>>>>"; done;
                

                while true; do sleep .25 && echo -ne "\r:Active:" && sleep .25 && echo -ne "\r:aCtive:" && sleep .25 && echo -ne "\r:acTive:" && sleep .25 && echo -ne "\r:actIve:" && sleep .25 && echo -ne "\r:actiVe:" && sleep .25 && echo -ne "\r:activE:"; done;
                

                可以在while循环中使用标志/变量来检查和显示进度的值/范围。

                【讨论】:

                  【解决方案24】:

                  https://github.com/extensionsapp/progre.sh

                  创建 40% 的进度:progreSh 40

                  【讨论】:

                    【解决方案25】:

                    这可以通过一种非常简单的方式实现:

                    • 使用 for 循环从 0 迭代到 100
                    • 每步睡眠 25 毫秒(0.25 秒)
                    • 将另一个= 符号附加到$bar 变量以使进度条更宽
                    • 回显进度条和百分比(\r 清理行并返回行首;-ne 使echo 不在末尾添加换行符并解析\r 特殊字符)
                    function progress {
                        bar=''
                        for (( x=0; x <= 100; x++ )); do
                            sleep 0.25
                            bar="${bar}="
                            echo -ne "$bar ${x}%\r"
                        done
                        echo -e "\n"
                    }
                    
                    $ progress
                    > ========== 10% # here: after 2.5 seconds
                    
                    $ progress
                    > ============================== 30% # here: after 7.5 seconds
                    

                    彩色进度条

                    function progress {
                        bar=''
                        for (( x=0; x <= 100; x++ )); do
                            sleep 0.05
                            bar="${bar} "
                    
                            echo -ne "\r"
                            echo -ne "\e[43m$bar\e[0m"
                    
                            local left="$(( 100 - $x ))"
                            printf " %${left}s"
                            echo -n "${x}%"
                        done
                        echo -e "\n"
                    }
                    

                    为了让进度条变得多彩,你可以使用格式化转义序列 - 这里进度条是黄色的:\e[43m,然后我们用\e[0m重置自定义设置,否则即使进度条是这样也会影响进一步的输入完成。

                    【讨论】:

                      【解决方案26】:

                      这仅适用于使用 gnome zenity。 Zenity 为 bash 脚本提供了一个很棒的原生接口: https://help.gnome.org/users/zenity/stable/

                      来自 Zenity 进度条示例:

                      #!/bin/sh
                      (
                      echo "10" ; sleep 1
                      echo "# Updating mail logs" ; sleep 1
                      echo "20" ; sleep 1
                      echo "# Resetting cron jobs" ; sleep 1
                      echo "50" ; sleep 1
                      echo "This line will just be ignored" ; sleep 1
                      echo "75" ; sleep 1
                      echo "# Rebooting system" ; sleep 1
                      echo "100" ; sleep 1
                      ) |
                      zenity --progress \
                        --title="Update System Logs" \
                        --text="Scanning mail logs..." \
                        --percentage=0
                      
                      if [ "$?" = -1 ] ; then
                              zenity --error \
                                --text="Update canceled."
                      fi
                      

                      【讨论】:

                        【解决方案27】:

                        我使用来自Creating string of repeated characters in shell script 的答案进行字符重复。对于需要显示进度条的脚本,我有两个相对较小的 bash 版本(例如,循环遍历许多文件,但对大 tar 文件或复制操作没有用处)。更快的一个包含两个函数,一个是为条形显示准备字符串:

                        preparebar() {
                        # $1 - bar length
                        # $2 - bar char
                            barlen=$1
                            barspaces=$(printf "%*s" "$1")
                            barchars=$(printf "%*s" "$1" | tr ' ' "$2")
                        }
                        

                        还有一个显示进度条:

                        progressbar() {
                        # $1 - number (-1 for clearing the bar)
                        # $2 - max number
                            if [ $1 -eq -1 ]; then
                                printf "\r  $barspaces\r"
                            else
                                barch=$(($1*barlen/$2))
                                barsp=$((barlen-barch))
                                printf "\r[%.${barch}s%.${barsp}s]\r" "$barchars" "$barspaces"
                            fi
                        }
                        

                        它可以用作:

                        preparebar 50 "#"
                        

                        这意味着为 bar 准备 50 个“#”字符的字符串,然后:

                        progressbar 35 80
                        

                        将显示对应于 35/80 比例的“#”字符数:

                        [#####################                             ]
                        

                        请注意,该函数会一遍又一遍地在同一行显示栏,直到您(或其他程序)打印一个换行符。如果您将 -1 作为第一个参数,则该条将被删除:

                        progressbar -1 80
                        

                        较慢的版本是多合一功能:

                        progressbar() {
                        # $1 - number
                        # $2 - max number
                        # $3 - number of '#' characters
                            if [ $1 -eq -1 ]; then
                                printf "\r  %*s\r" "$3"
                            else
                                i=$(($1*$3/$2))
                                j=$(($3-i))
                                printf "\r[%*s" "$i" | tr ' ' '#'
                                printf "%*s]\r" "$j"
                            fi
                        }
                        

                        它可以用作(与上面相同的示例):

                        progressbar 35 80 50
                        

                        如果您需要 stderr 上的进度条,只需在每个 printf 命令的末尾添加&gt;&amp;2

                        【讨论】:

                          【解决方案28】:

                          使用上面列出的建议,我决定实现自己的进度条。

                          #!/usr/bin/env bash
                          
                          main() {
                            for (( i = 0; i <= 100; i=$i + 1)); do
                              progress_bar "$i"
                              sleep 0.1;
                            done
                            progress_bar "done"
                            exit 0
                          }
                          
                          progress_bar() {
                            if [ "$1" == "done" ]; then
                              spinner="X"
                              percent_done="100"
                              progress_message="Done!"
                              new_line="\n"
                            else
                              spinner='/-\|'
                              percent_done="${1:-0}"
                              progress_message="$percent_done %"
                            fi
                          
                            percent_none="$(( 100 - $percent_done ))"
                            [ "$percent_done" -gt 0 ] && local done_bar="$(printf '#%.0s' $(seq -s ' ' 1 $percent_done))"
                            [ "$percent_none" -gt 0 ] && local none_bar="$(printf '~%.0s' $(seq -s ' ' 1 $percent_none))"
                          
                            # print the progress bar to the screen
                            printf "\r Progress: [%s%s] %s %s${new_line}" \
                              "$done_bar" \
                              "$none_bar" \
                              "${spinner:x++%${#spinner}:1}" \
                              "$progress_message"
                          }
                          
                          main "$@"
                          

                          【讨论】:

                          • 不错!为了让它工作,我不得不将percent_none="$(( 100 - "$percent_done" ))" 改为percent_none="$(( 100 - $percent_done))"
                          【解决方案29】:

                          具有随机颜色、可操作字符串和日期的灵活版本。

                          function spinner() {
                            local PID="$1"
                            local str="${2:-Processing!}"
                            local delay="0.1"
                            # tput civis  # hide cursor
                            while ( kill -0 $PID 2>/dev/null )
                              do
                                printf "\e[38;5;$((RANDOM%257))m%s\r\e[0m" "[$(date '+%d/%m/%Y %H:%M:%S')][ ?  ?  ? $str ?  ?  ? ]"; sleep "$delay"
                                printf "\e[38;5;$((RANDOM%257))m%s\r\e[0m" "[$(date '+%d/%m/%Y %H:%M:%S')][ ?  ?  ? $str ?  ?  ? ]"; sleep "$delay"
                                printf "\e[38;5;$((RANDOM%257))m%s\r\e[0m" "[$(date '+%d/%m/%Y %H:%M:%S')][ ?  ?  ? $str ?  ?  ? ]"; sleep "$delay"
                            done
                            printf "\e[38;5;$((RANDOM%257))m%s\r\e[0m" "[$(date '+%d/%m/%Y %H:%M:%S')][ ✅  ✅  ✅   Done!   ✅  ✅  ✅ ]"; sleep "$delay"
                            # tput cnorm  # restore cursor
                          
                            return 0
                          }
                          

                          用法:

                          # your long running proccess pushed to the background
                          sleep 20 &
                          
                          # spinner capture-previous-proccess-id string
                          spinner $! 'Working!'
                          

                          输出示例:

                          [04/06/2020 03:22:24][ ?  ?  ? Seeding! ?  ?  ? ]
                          

                          【讨论】:

                            【解决方案30】:

                            我需要一个适合弹出气泡消息 (notify-send) 的进度条来表示电视音量。最近在用python写一个音乐播放器,电视画面大部分时间都是关闭的。

                            终端的示例输出


                            Bash 脚本

                            #!/bin/bash
                            
                            # Show a progress bar at step number $1 (from 0 to 100)
                            
                            
                            function is_int() { test "$@" -eq "$@" 2> /dev/null; } 
                            
                            # Parameter 1 must be integer
                            if ! is_int "$1" ; then
                               echo "Not an integer: ${1}"
                               exit 1
                            fi
                            
                            # Parameter 1 must be >= 0 and <= 100
                            if [ "$1" -ge 0 ] && [ "$1" -le 100 ]  2>/dev/null
                            then
                                :
                            else
                                echo bad volume: ${1}
                                exit 1
                            fi
                            
                            # Main function designed for quickly copying to another program 
                            Main () {
                            
                                Bar=""                      # Progress Bar / Volume level
                                Len=25                      # Length of Progress Bar / Volume level
                                Div=4                       # Divisor into Volume for # of blocks
                                Fill="▒"                    # Fill up to $Len
                                Arr=( "▉" "▎" "▌" "▊" )     # UTF-8 left blocks: 7/8, 1/4, 1/2, 3/4
                            
                                FullBlock=$((${1} / Div))   # Number of full blocks
                                PartBlock=$((${1} % Div))   # Size of partial block (array index)
                            
                                while [[ $FullBlock -gt 0 ]]; do
                                    Bar="$Bar${Arr[0]}"     # Add 1 full block into Progress Bar
                                    (( FullBlock-- ))       # Decrement full blocks counter
                                done
                            
                                # If remainder zero no partial block, else append character from array
                                if [[ $PartBlock -gt 0 ]]; then
                                    Bar="$Bar${Arr[$PartBlock]}"
                                fi
                            
                                while [[ "${#Bar}" -lt "$Len" ]]; do
                                    Bar="$Bar$Fill"         # Pad Progress Bar with fill character
                                done
                            
                                echo Volume: "$1 $Bar"
                                exit 0                      # Remove this line when copying into program
                            } # Main
                            
                            Main "$@"
                            

                            测试 bash 脚本

                            使用此脚本测试终端中的进度条。

                            #!/bin/bash
                            
                            # test_progress_bar3
                            
                            Main () {
                            
                                tput civis                              # Turn off cursor
                                for ((i=0; i<=100; i++)); do
                                    CurrLevel=$(./progress_bar3 "$i")   # Generate progress bar 0 to 100
                                    echo -ne "$CurrLevel"\\r            # Reprint overtop same line
                                    sleep .04
                                done
                                echo -e \\n                             # Advance line to keep last progress
                                echo "$0 Done"
                                tput cnorm                              # Turn cursor back on
                            } # Main
                            
                            Main "$@"
                            

                            TL;DR

                            本节详细介绍了如何使用notify-send 将垃圾邮件弹出气泡消息快速发送到桌面。这是必需的,因为音量可以在一秒钟内改变很多次,并且默认的气泡消息行为是让消息在桌面上停留数秒。

                            弹出气泡消息示例

                            弹出气泡消息 bash 代码

                            从上面的脚本中,main 函数被复制到一个名为 VolumeBar 的新函数中,该函数位于名为 tvpowered 的现有 bash 脚本中。复制的main 函数中的exit 0 命令已被删除。

                            这是如何调用它并让 Ubuntu 的 notify-send 命令知道我们将发送垃圾邮件弹出气泡消息:

                            VolumeBar $CurrVolume
                            # Ask Ubuntu: https://askubuntu.com/a/871207/307523
                            notify-send --urgency=critical "tvpowered" \
                                -h string:x-canonical-private-synchronous:volume \
                                --icon=/usr/share/icons/gnome/48x48/devices/audio-speakers.png \
                                "Volume: $CurrVolume $Bar"
                            

                            这是告诉notify-send 立即替换最后一个弹出气泡的新行:

                            -h string:x-canonical-private-synchronous:volume \
                            

                            volume 将弹出气泡消息组合在一起,该组中的新消息立即替换之前的消息。您可以使用anything 代替volume

                            【讨论】:

                              猜你喜欢
                              • 2020-11-13
                              • 2015-02-15
                              • 2021-07-27
                              • 2022-10-17
                              • 1970-01-01
                              • 1970-01-01
                              • 2020-07-20
                              • 1970-01-01
                              • 1970-01-01
                              相关资源
                              最近更新 更多