如何在shell脚本中添加进度条?

发布于 2024-07-07 17:39:02 字数 133 浏览 7 评论 0原文

在 bash 或 *NIX 中的任何其他 shell 中编写脚本时,如果运行的命令需要花费几秒钟的时间,则需要进度条。

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

您建议使用哪些方法向 shell 脚本添加进度条?

When scripting in bash or any other shell in *NIX, while running a command that will take more than a few seconds, a progress bar is needed.

For example, copying a big file, opening a big tar file.

What ways do you recommend to add progress bars to shell scripts?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(30

紙鸢 2024-07-14 17:39:03

还没有看到任何类似的东西,而且这里的所有自定义函数似乎都只专注于渲染,所以...下面是我非常简单的 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< /code> - 打印到终端(即到 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/bash

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 - a用于以给定格式打印内容的内置函数
  • printf %50s - 不打印任何内容,只用 50 个空格填充
  • tr ' ' '#' - 将每个空格转换为井号

这是如何使用它:

#!/bin/bash

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}。 此语法的技术术语是参数扩展,这是一种内置的 shell 功能,允许操作变量(参数),例如使用 : 修剪字符串,但也可以做其他事情 - 它不会生成子 shell。 我能想到的关于参数扩展的最突出的描述(不完全兼容 POSIX,但可以让读者很好地理解这个概念)位于 man bash 页面中。
  2. $():在上面的示例中我使用foo=$(bar)。 它在子进程中生成一个单独的 shell(也称为 Subshel​​l),在其中运行 bar 命令并将其标准输出分配给 $foo多变的。 它与进程替换不同,并且与管道 (|) 完全不同。 最重要的是,它有效。 有人说应该避免这种情况,因为它很慢。 我认为这在这里“没问题”,因为这段代码试图可视化的任何内容都会持续足够长的时间,需要一个进度条。 换句话说,子shell 不是瓶颈。 调用子 shell 还可以让我省去解释为什么 return 与大多数人想象的不同、什么是退出状态以及为什么从 shell 中的函数传递值是这样的工作。一般来说,这不是 shell 函数所擅长的。 要了解更多信息,我再次强烈推荐 man bash 页面。

故障排除

如果您的 shell 实际上运行的是 sh 而不是 bash,或者是非常旧的 bash,例如默认的 osx,则它可能会因 echo -ne "\r${BAR:0:$i}" 而阻塞。 确切的错误是错误替换。 如果您遇到这种情况,根据评论部分,您可以使用 echo -ne "\r$(expr "x$name" : "x.\{0,$num_skip\}\(.\{0 ,$num_keep\}\)")" 进行更便携的 posix 兼容/可读性较差的子字符串匹配。

一个完整的、有效的 /bin/sh 示例:

#!/bin/sh

src=100
tgt=0

get_work_todo() {
    echo $src
}

do_work() {
    echo "$(( $1 + 1 ))"
}

BAR_length=50
BAR_character='#'
BAR=$(printf %${BAR_length}s | tr ' ' $BAR_character)
work_todo=$(get_work_todo)             # how much there is to do
work_done=0
while true; do
    work_done="$(do_work $work_done)"
    i=$(( $work_done * $BAR_length / $work_todo ))
    n=$(( $BAR_length - $i ))
    printf "\r$(expr "x$BAR" : "x.\{0,$n\}\(.\{0,$i\}\)")"
    if [ $work_done = $work_todo ]; then
        echo "\n"
        break;
    fi
    sleep .1
done

Haven't seen anything similar and all custom functions here seem to focus on rendering alone so... my very simple POSIX compliant solution below with step by step explanations because this question isn't trivial.

TL;DR

Rendering the progress bar is very easy. Estimating how much of it should render is a different matter. This is how to render (animate) the progress bar - you can copy&paste this example to a file and run it:

#!/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} - values from 1 to 20
  • echo - print to terminal (i.e. to stdout)
  • echo -n - print without new line at the end
  • echo -e - interpret special characters while printing
  • "\r" - carriage return, a special char to return to the beginning of the line

You can make it render any content at any speed so this method is very universal, e.g. often used for visualization of "hacking" in silly movies, no kidding.

Full answer (from zero to working example)

The meat of the problem is how to determine the $i value, i.e. how much of the progress bar to display. In the above example I just let it increment in for loop to illustrate the principle but a real life application would use an infinite loop and calculate the $i variable on each iteration. To make said calculation it needs the following ingredients:

  1. how much work there is to be done
  2. how much work has been done so far

In case of cp it needs the size of a source file and the size of the target file:

#!/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) - run bar in a subprocess and save its stdout to $foo
  • stat - print file stats to stdout
  • stat -c - print a formatted value
  • %s - format for total size

In case of operations like file unpacking, calculating the source size is slightly more difficult but still as easy as getting the size of an uncompressed file:

#!/bin/sh
src_size=$(gzip -l "$src" | tail -n1 | tr -s ' ' | cut -d' ' -f3)
  • gzip -l - print info about zip archive
  • tail -n1 - work with 1 line from the bottom
  • tr -s ' ' - translate multiple spaces into one ("squeeze" them)
  • cut -d' ' -f3 - cut 3rd space-delimited field (column)

Here's the meat of the problem I mentioned before. This solution is less and less general. All calculations of the actual progress are tightly bound to the domain you're trying to visualize, is it a single file operation, a timer countdown, a rising number of files in a directory, operation on multiple files, etc., therefore, it can't be reused. The only reusable part is progress bar rendering. To reuse it you need to abstract it and save in a file (e.g. /usr/lib/progress_bar.sh), then define functions that calculate input values specific to your domain. This is how a generalized code could look like (I also made the $BAR dynamic because people were asking for it, the rest should be clear by now):

#!/bin/bash

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 - a builtin for printing stuff in a given format
  • printf %50s - print nothing but pad it with 50 spaces
  • tr ' ' '#' - translate every space to hash sign

And this is how you'd use it:

#!/bin/bash

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

Obviously you can wrap this in a function, rewrite to work with piped streams, grab forked process ID with $! and pass it to progress_bar.sh so it could guess how to calculate work to do and work done, whatever's your poison.

Side notes

I get asked about these two things most often:

  1. ${}: in above examples I use ${foo:A:B}. The technical term for this syntax is Parameter Expansion, a built-in shell functionality that allows to manipulate a variable (parameter), e.g. to trim a string with : but also to do other things - it does not spawn a subshell. The most prominent description of parameter expansion I can think of (that isn't fully POSIX compatible but lets the reader understand the concept well) is in the man bash page.
  2. $(): in above examples I use foo=$(bar). It spawns a separate shell in a subprocess (a.k.a. a Subshell), runs the bar command in it and assigns its standard output to a $foo variable. It's not the same as Process Substitution and it's something entirely different than pipe (|). Most importantly, it works. Some say this should be avoided because it's slow. I argue this is "a okay" here because whatever this code is trying to visualise lasts long enough to require a progress bar. In other words, subshells are not the bottleneck. Calling a subshell also saves me the effort of explaining why return isn't what most people think it is, what is an Exit Status and why passing values from functions in shells is not what shell functions are good at in general. To find out more about all of it I, again, highly recommend the man bash page.

Troubleshooting

If your shell is actually running sh instead of bash, or really old bash, like default osx, it may choke on echo -ne "\r${BAR:0:$i}". The exact error is Bad substitution. If this happens to you, per the comment section, you can instead use echo -ne "\r$(expr "x$name" : "x.\{0,$num_skip\}\(.\{0,$num_keep\}\)")" to do a more portable posix-compatible / less readable substring match.

A complete, working /bin/sh example:

#!/bin/sh

src=100
tgt=0

get_work_todo() {
    echo $src
}

do_work() {
    echo "$(( $1 + 1 ))"
}

BAR_length=50
BAR_character='#'
BAR=$(printf %${BAR_length}s | tr ' ' $BAR_character)
work_todo=$(get_work_todo)             # how much there is to do
work_done=0
while true; do
    work_done="$(do_work $work_done)"
    i=$(( $work_done * $BAR_length / $work_todo ))
    n=$(( $BAR_length - $i ))
    printf "\r$(expr "x$BAR" : "x.\{0,$n\}\(.\{0,$i\}\)")"
    if [ $work_done = $work_todo ]; then
        echo "\n"
        break;
    fi
    sleep .1
done
海夕 2024-07-14 17:39:03

雇用(浮点)进度条

序言

抱歉,这个不太短的答案。 在这个答案中,我将使用整数来渲染浮点,UTF-8< /strong> 用于更精细地渲染进度条的字体,以及并行化另一个任务(sha1sum)为了跟随他的进展,所有这些都使用 和 < em>没有分叉

对于凤仙花:请在现在开始!(在中间)测试代码(在新的终端窗口中复制/粘贴) ,与

  • 任一:最后一个动画演示(即将结束。),
  • 实际示例(最后)。

这里的所有演示都使用read -t; && 中断而不是睡眠。 因此,通过按 Return 键可以很好地停止所有循环。

简介

又一个 Bash 进度条...

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

1.避免分叉!

因为进度条的目的是在其他进程运行时运行,所以这一定是一个好的进程...

所以避免使用分叉 不需要时。 示例:而不是

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

使用

printf -v mysmiley '%b' \\U1F60E

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

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 似乎比使用内置 fork 慢了 135 倍/贵了 135 倍。代码>printf -v。

那么

echo $mysmiley 

Hires (floating point) progress bar

Preamble

Sorry for this not so short answer. In this answer I will use integer to render floating point, UTF-8 fonts for rendering progress bar more finely, and parallelise another task (sha1sum) in order to follow his progression, all of this with minimal resource footprint using pure and no forks.

For impatiens: Please test code (copy/paste in a new terminal window) at Now do it! (in the middle), with

  • either: Last animated demo (near end of this.),
  • either Practical sample (at end).

All demos here use read -t <float seconds> && break instead of sleep. So all loop could be nicely stopped by hitting Return key.

Introduction

Yet Another Bash Progress Bar...

As there is already a lot of answer here, I want to add some hints about performances and precision.

1. Avoid forks!

Because a progress bar are intented to run while other process are working, this must be a nice process...

So avoid using forks when not needed. Sample: instead of

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

Use

printf -v mysmiley '%b' \\U1F60E

Explanation: When you run var=$(command), you initiate a new process to execute command and send his output to variable $var once terminated. This is very resource expensive. Please compare:

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

On my host, same work of assigning $mysmiley (just 2500 time), seem ~135x slower / more expensive by using fork than by using built-in printf -v.

Then

echo $mysmiley 
????

So your function have to not print (or output) anything. Your function have to attribute his answer to a variable.

2. Use integer as pseudo floating point

Here is a very small and quick function to compute percents from integers, with integer and answer a pseudo floating point number:

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

Usage:

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

3. Hires console graphic using UTF-8: ▏ ▎ ▍ ▌ ▋ ▊ ▉ █

To render this characters using bash, you could:

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

or

printf %b\  \\U258{{f..a},9,8}
▏ ▎ ▍ ▌ ▋ ▊ ▉ █

Then we have to use 8x string width as graphic width.

Now do it!

This function is named percentBar because it render a bar from argument submited in percents (floating):

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"
}

Usage:

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

To show little differences:

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

With colors

As rendered variable is a fixed widht string, using color is easy:

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

Bar with color

Little animation:

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

PercentBar animation

Last animated demo

Another demo showing different sizes and colored output:

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

Could produce something like this:

Last animation percentBar animation

Practical GNU/Linux sample 1: kind of sleep with progress bar

Rewrite feb 2023: Turn into more usefull displaySleep function suitable to use as displayed timeout read:

This sleep show a progress bar with 50 refresh by seconds (tunnable)

percent(){ local p=00$(($1*100000/$2));printf -v "$3" %.2f ${p::-3}.${p: -3};}
displaySleep() {
    local -i refrBySeconds=50
    local -i _start=${EPOCHREALTIME/.} reqslp target crtslp crtp cols cpos dlen
    local strng percent prctbar tleft
    [[ $COLUMNS ]] && cols=${COLUMNS} || read -r cols < <(tput cols)
    refrBySeconds=' 1000000 / refrBySeconds '
    printf -v strng %.6f $1
    printf '\E[6n' && IFS=\; read -sdR _ cpos
    dlen=${#strng}-1  cols=' cols - dlen - cpos -1 '
    printf \\e7
    reqslp=10#${strng/.} target=reqslp+_start
    for ((;${EPOCHREALTIME/.}<target;)){
        crtp=${EPOCHREALTIME/.}
        crtslp='( target - crtp ) > refrBySeconds? refrBySeconds: target - crtp'
        strng=00000$crtslp  crtp+=-_start
        printf -v strng %.6f ${strng::-6}.${strng: -6}
        percent $crtp $reqslp percent
        percentBar $percent $cols prctbar
        tleft=00000$((reqslp-crtp))
        printf '\e8\e[36;48;5;23m%s\e[0m%*.4fs' \
               "$prctbar" "$dlen" ${tleft::-6}.${tleft: -6}
        IFS= read -rsn1 -t $strng ${2:-_} && { echo; return;}
    }
    percentBar 100 $cols prctbar
    printf '\e8\e[36;48;5;30m%s\e[0m%*.4fs\n' "$prctbar" "$dlen" 0
    false
}

This will keep current cursor position to fill only the rest of line (full line if current cursor position is 1). This could be useful for displaying some kind of prompt:

enter image description here

Practical GNU/Linux sample 2: sha1sum with progress bar

Under linux, you could find a lot of usefull infos under /proc pseudo filesystem, so using previoulsy defined functions percentBar and percent, here is sha1progress:

percent(){ local p=00$(($1*100000/$2));printf -v "$3" %.2f ${p::-3}.${p: -3};}
sha1Progress() { 
    local -i totsize crtpos cols=$(tput cols) sha1in sha1pid
    local sha1res percent prctbar
    exec {sha1in}< <(exec sha1sum -b - <"$1")
    sha1pid=$!
    read -r totsize < <(stat -Lc %s "$1")
    while ! read -ru $sha1in -t .025 sha1res _; do
        read -r _ crtpos < /proc/$sha1pid/fdinfo/0
        percent $crtpos $totsize percent
        percentBar $percent $((cols-8)) prctbar
        printf '\r\e[44;38;5;25m%s\e[0m%6.2f%%' "$prctbar" $percent;

    done
    printf "\r%s  %s\e[K\n" $sha1res "$1"
}

Of course, 25 ms timeout mean approx 40 refresh per second. This could look overkill, but work fine on my host, and anyway, this can be tunned.

sha1Progress sample

Explanation:

  • exec {sha1in}< create a new file descriptor for the output of
  • <( ... ) forked task run in background
  • sha1sum -b - <"$1" ensuring input came from STDIN (fd/0)
  • while ! read -ru $sha1in -t .025 sha1res _ While no input read from subtask, in 25 ms...
  • /proc/$sha1pid/fdinfo/0 kernel variable showing information about file descriptor 0 (STDIN) of task $sha1pid
穿透光 2024-07-14 17:39:03

APT 样式进度条(不会破坏正常输出)

在此处输入图像描述

编辑:有关更新版本,请检查我的 github页

我对这个问题的回答不满意。 我个人想要的是 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}"
}

APT style progress bar (Does not break normal output)

enter image description here

EDIT: For an updated version check my github page

I was not satisfied with the responses on this question. What I was personally looking for was a fancy progress bar as is seen by APT.

I had a look at the C source code for APT and decided to write my own equivalent for bash.

This progress bar will stay nicely at the bottom of the terminal and will not interfere with any output sent to the terminal.

Please do note that the bar is currently fixed at 100 characters wide. If you want scale it to the size of the terminal, this is fairly easy to accomplish as well (The updated version on my github page handles this well).

I will post my script here.
Usage example:

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

The script (I strongly recommend the version on my github instead):

#!/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}"
}
深爱不及久伴 2024-07-14 17:39:03

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

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

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

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

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

GNU tar has a useful option which gives a functionality of a simple progress bar.

(...) Another available checkpoint action is ‘dot’ (or ‘.’). It instructs tar to print a single dot on the standard listing stream, e.g.:

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

The same effect may be obtained by:

$ tar -c --checkpoint=.1000 /var
避讳 2024-07-14 17:39:03

一种更简单的方法,可以使用 pipelineview ( pv ) 实用程序在我的系统上运行。

srcdir=$1
outfile=$2


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

A simpler method that works on my system using the pipeview ( pv ) utility.

srcdir=$1
outfile=$2


tar -Ocf - $srcdir | pv -i 1 -w 50 -berps `du -bs $srcdir | awk '{print $1}'` | 7za a -si $outfile
眼泪都笑了 2024-07-14 17:39:03

下面是它的外观

上传文件

[##################################################] 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"
}

使用示例

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

在下面的示例中,最大值为 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"

Here is how it might look

Uploading a file

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

Waiting for a job to complete

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

Simple function that implements it

You can just copy-paste it to your script. It does not require anything else to work.

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"
}

Usage example

Here, we upload a file and redraw the progress bar at each iteration. It does not matter what job is actually performed as long as we can get 2 values: max value and current value.

In the example below the max value is file_size and the current value is supplied by some function and is called 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"
一笔一画续写前缘 2024-07-14 17:39:03

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

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

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

This lets you visualize that a command is still executing:

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

This will create an infinite while loop that executes in the background and echoes a "." every second. This will display . in the shell. Run the tar command or any a command you want. When that command finishes executing then kill the last job running in the background - which is the infinite while loop.

指尖上的星空 2024-07-14 17:39:03

我需要一个进度条来迭代 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%)

I needed a progress bar for iterating over the lines in a csv file. Was able to adapt cprn's code into something useful for me:

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

Looks like this:

[##----------------------------] 17128/218210 (7%)
何时共饮酒 2024-07-14 17:39:03

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

对于像 tar 这样的东西,您可以使用 -v 开关并将输出通过管道传输到一个程序,该程序会为其读取的每一行更新一个小动画。 当 tar 写出一个已解开的文件列表时,程序可以更新动画。 要计算完成百分比,您必须知道文件数量并计算行数。

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

Most unix commands will not give you the sort of direct feedback from which you can do this.
Some will give you output on stdout or stderr that you can use.

For something like tar you could use the -v switch and pipe the output to a program that updates a small animation for each line it reads. As tar writes out a list of files it's unravelled the program can update the animation. To do a percent complete you would have to know the number of files and count the lines.

cp doesn't give this sort of output as far as I know. To monitor the progress of cp you would have to monitor the source and destination files and watch the size of the destination. You could write a small c program using the stat (2) system call to get the file size. This would read the size of the source then poll the destination file and update a % complete bar based on the size of the file written to date.

云醉月微眠 2024-07-14 17:39:03

根据 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

享受

Based on the work of Edouard Lopez, I created a progress bar that fits the size of the screen, whatever it is. Check it out.

enter image description here

It's also posted on 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

Enjoy

海未深 2024-07-14 17:39:03

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

终端的示例输出

test_progress_bar3.gif


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 将垃圾弹出气泡消息快速发送到桌面。 这是必需的,因为音量级别每秒可能会改变多次,并且默认的气泡消息行为是消息在桌面上停留很多秒。

弹出气泡消息示例

tvpowered.gif

弹出气泡消息 bash 代码

从上面的脚本中,main 函数被复制到名为 VolumeBar 的现有 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 立即替换最后一个弹出窗口bubble:

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

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

I needed a progress bar that would fit in popup bubble message (notify-send) to represent TV volume level. Recently I've been writing a music player in python and the TV picture is turned off most of the time.

Sample output from terminal

test_progress_bar3.gif


Bash script

#!/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 "$@"

Test bash script

Use this script to test the progress bar in the terminal.

#!/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

This section details how notify-send is used to quickly spam popup bubble messages to the desktop. This is required because volume level can change many times a second and the default bubble message behavior is for a message to stay on the desktop for many seconds.

Sample popup bubble message

tvpowered.gif

Popup bubble message bash code

From the script above the main function was copied to a new functioned called VolumeBar in an existing bash script called tvpowered. The exit 0 command in the copied main function was removed.

Here's how to call it and let Ubuntu's notify-send command know we will be spamming popup bubble message:

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"

This is the new line which tells notify-send to immediately replace last popup bubble:

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

volume groups the popup bubble messages together and new messages in this group immediately replaces the previous. You can use anything instead of volume.

待天淡蓝洁白时 2024-07-14 17:39:03

我的解决方案显示了 tarball 的百分比
目前正在解压缩和写入。 我用这个
写出 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}
}

My solution displays the percentage of the tarball that
is currently being uncompressed and written. I use this
when writing out 2GB root filesystem images. You really
need a progress bar for these things. What I do is use
gzip --list to get the total uncompressed size of the
tarball. From that I calculate the blocking-factor needed
to divide the file into 100 parts. Finally, I print a
checkpoint message for each block. For a 2GB file this
gives about 10MB a block. If that is too big then you can
divide the BLOCKING_FACTOR by 10 or 100, but then it's
harder to print pretty output in terms of a percentage.

Assuming you are using Bash then you can use the
following shell function

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}
}
谁把谁当真 2024-07-14 17:39:03

首先bar并不是唯一的一个管道进度表。 另一个(可能更广为人知)是 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 ,一个有用的技巧是使用 进程替换

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

进程替换是一个 bash 魔法,它会创建临时 fifo 管道文件 / dev/fd/ 并通过此管道从运行的进程(括号内)连接 stdout,并且复制看到它就像普通文件一样(有一个例外,它只能向前读取)。

更新:

bar 命令本身也允许复制。 在 man bar 之后:

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

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

First of all bar is not the only one pipe progress meter. The other (maybe even more known) is pv (pipe viewer).

Secondly bar and pv can be used for example like this:

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

or even:

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

one useful trick if you want to make use of bar and pv in commands that are working with files given in arguments, like e.g. copy file1 file2, is to use process substitution:

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

Process substitution is a bash magic thing that creates temporary fifo pipe files /dev/fd/ and connect stdout from runned process (inside parenthesis) through this pipe and copy sees it just like an ordinary file (with one exception, it can only read it forwards).

Update:

bar command itself allows also for copying. After man bar:

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

But process substitution is in my opinion more generic way to do it. An it uses cp program itself.

柠北森屋 2024-07-14 17:39:03

许多答案描述了编写自己的命令来打印 '\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

Many answers describe writing your own commands for printing out '\r' + $some_sort_of_progress_msg. The problem sometimes is that printing out hundreds of these updates per second will slow down the process.

However, if any of your processes produce output (eg 7z a -r newZipFile myFolder will output each filename as it compresses it) then a simpler, fast, painless and customisable solution exists.

Install the python module 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

Help: tqdm -h. An example using more options:

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

As a bonus you can also use tqdm to wrap iterables in python code.

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

天邊彩虹 2024-07-14 17:39:03

我更喜欢将 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"

所以我可以输入:

等待“34 分钟”“预热烤箱”

等待“12 月 31 日”“新年快乐”

I prefer to use dialog with the --gauge param. Is used very often in .deb package installations and other basic configuration stuff of many distros. So you don't need to reinvent the wheel... again

Just put an int value from 1 to 100 @stdin. One basic and silly example:

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

I have this /bin/Wait file (with chmod u+x perms) for cooking purposes :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"

So I can put:

Wait "34 min" "warm up the oven"

or

Wait "dec 31" "happy new year"

我最亲爱的 2024-07-14 17:39:03

这仅适用于使用 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

This is only applicable using gnome zenity. Zenity provides a great native interface to bash scripts:
https://help.gnome.org/users/zenity/stable/

From Zenity Progress Bar Example:

#!/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
离鸿 2024-07-14 17:39:03

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

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;

OR

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;

OR

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;

OR

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 循环检查并显示进度值/程度。

To indicate progress of activity, try the following commands:

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;

OR

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;

OR

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;

OR

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;

One can use flags/variables inside the while loop to check and display the value/extent of progress.

梦初启 2024-07-14 17:39:03

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

创建 40% 的进度:progreSh 40

在此处输入图像描述

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

Create 40 percent progress: progreSh 40

enter image description here

泼猴你往哪里跑 2024-07-14 17:39:03

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

  • 使用 for 循环从 0 迭代到 100,
  • 每一步休眠 25 毫秒(0.25 秒),
  • 将另一个 $bar 变量附加到 $bar 变量上>= 符号使进度条更宽
  • 回显进度条和百分比(\r 清理行并返回到行的开头;-ne 使 < code>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重置自定义设置,否则即使进度条也会影响进一步的输入酒吧完成了。

It may be achieved in a pretty simple way:

  • iterate from 0 to 100 with for loop
  • sleep every step for 25ms (0.25 second)
  • append to the $bar variable another = sign to make the progress bar wider
  • echo progress bar and percentage (\r cleans line and returns to the beginning of the line; -ne makes echo doesn't add newline at the end and parses \r special character)
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

COLORED PROGRESS BAR

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"
}

To make a progress bar colorful, you can use formatting escape sequence - here the progress bar is yellow: \e[43m, then we reset custom settings with \e[0m, otherwise it would affect further input even when the progress bar is done.

custom progress bar

忆悲凉 2024-07-14 17:39:03

对我来说,迄今为止最容易使用且最好看的是命令 pvbar 就像有人已经写过的那样,

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

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

pv 你可以这样做:

dd if=" $input_drive_path"| 光伏 | 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

for me easiest to use and best looking so far is command pv or bar like some guy already wrote

for example: need to make a backup of entire drive with dd

normally you use dd if="$input_drive_path" of="$output_file_path"

with pv you can make it like this :

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

and the progress goes directly to STDOUT as this:

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

after it is done summary comes up

    15654912+0 records in
    15654912+0 records out
    8015314944 bytes (8.0 GB) copied, 2020.49 s, 4.0 MB/s
病毒体 2024-07-14 17:39:03

我使用了 在 shell 脚本中创建重复字符的字符串 对于字符重复。 我有两个相对较小的 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 "#"

这意味着为条形准备带有 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 上的进度条,只需添加 >&2 在每个 printf 命令的末尾。

I used an answer from Creating string of repeated characters in shell script for char repeating. I have two relatively small bash versions for scripts that need to display progress bar (for example, a loop that goes through many files, but not useful for big tar files or copy operations). The faster one consists of two functions, one to prepare the strings for bar display:

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

and one to display a progress bar:

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
}

It could be used as:

preparebar 50 "#"

which means prepare strings for bar with 50 "#" characters, and after that:

progressbar 35 80

will display the number of "#" characters that corresponds to 35/80 ratio:

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

Be aware that function displays the bar on the same line over and over until you (or some other program) prints a newline. If you put -1 as first parameter, the bar would be erased:

progressbar -1 80

The slower version is all in one function:

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
}

and it can be used as (the same example as above):

progressbar 35 80 50

If you need progressbar on stderr, just add >&2 at the end of each printf command.

山有枢 2024-07-14 17:39:03

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

#!/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 "$@"

Using suggestions listed above, I decided to implement my own progress bar.

#!/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 "$@"
小巷里的女流氓 2024-07-14 17:39:03

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

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')][

Flexible version with randomized colors, a string to manipulate and date.

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
}

Usage:

# your long running proccess pushed to the background
sleep 20 &

# spinner capture-previous-proccess-id string
spinner $! 'Working!'

output example:

[04/06/2020 03:22:24][ ????  ????  ???? Seeding! ????  ????  ???? ]
幸福%小乖 2024-07-14 17:39:03

我利用以下优势为嵌入式系统制作了一个纯 shell 版本:

  • /usr/bin/dd 的 SIGUSR1 信号处理功能。

    基本上,如果您发送“kill SIGUSR1 $(pid_of_running_dd_process)”,它会输出
    吞吐量速度和传输量的摘要。

  • 后台 dd,然后定期查询它的更新,并生成
    哈希标记就像老式 ftp 客户端所使用的那样。

  • 使用 /dev/stdout 作为非标准输出友好程序(如 scp)的目标

最终结果允许您进行任何文件传输操作并获取看起来像老式 FTP“哈希”输出的进度更新,您只需为每个 X 字节获取一个哈希标记。

这很难说是生产质量的代码,但你明白了。 我觉得很可爱。

无论如何,实际的字节数可能无法正确反映在散列数中 - 根据舍入问题,您可能会有更多或更少的散列数。 不要将其用作测试脚本的一部分,它只是美观而已。 而且,是的,我知道这是非常低效的 - 这是一个 shell 脚本,我不会为此道歉。

最后提供了 wget、scp 和 tftp 的示例。 它应该适用于任何发出数据的东西。 确保对标准输出不友好的程序使用 /dev/stdout。

#!/bin/sh
#
# Copyright (C) Nathan Ramella ([email protected]) 2010 
# LGPLv2 license
# If you use this, send me an email to say thanks and let me know what your product
# is so I can tell all my friends I'm a big man on the internet!

progress_filter() {

        local START=$(date +"%s")
        local SIZE=1
        local DURATION=1
        local BLKSZ=51200
        local TMPFILE=/tmp/tmpfile
        local PROGRESS=/tmp/tftp.progress
        local BYTES_LAST_CYCLE=0
        local BYTES_THIS_CYCLE=0

        rm -f ${PROGRESS}

        dd bs=$BLKSZ of=${TMPFILE} 2>&1 \
                | grep --line-buffered -E '[[:digit:]]* bytes' \
                | awk '{ print $1 }' >> ${PROGRESS} &

        # Loop while the 'dd' exists. It would be 'more better' if we
        # actually looked for the specific child ID of the running 
        # process by identifying which child process it was. If someone
        # else is running dd, it will mess things up.

        # My PID handling is dumb, it assumes you only have one running dd on
        # the system, this should be fixed to just get the PID of the child
        # process from the shell.

        while [ $(pidof dd) -gt 1 ]; do

                # PROTIP: You can sleep partial seconds (at least on linux)
                sleep .5    

                # Force dd to update us on it's progress (which gets
                # redirected to $PROGRESS file.
                # 
                # dumb pid handling again
                pkill -USR1 dd

                local BYTES_THIS_CYCLE=$(tail -1 $PROGRESS)
                local XFER_BLKS=$(((BYTES_THIS_CYCLE-BYTES_LAST_CYCLE)/BLKSZ))

                # Don't print anything unless we've got 1 block or more.
                # This allows for stdin/stderr interactions to occur
                # without printing a hash erroneously.

                # Also makes it possible for you to background 'scp',
                # but still use the /dev/stdout trick _even_ if scp
                # (inevitably) asks for a password. 
                #
                # Fancy!

                if [ $XFER_BLKS -gt 0 ]; then
                        printf "#%0.s" $(seq 0 $XFER_BLKS)
                        BYTES_LAST_CYCLE=$BYTES_THIS_CYCLE
                fi
        done

        local SIZE=$(stat -c"%s" $TMPFILE)
        local NOW=$(date +"%s")

        if [ $NOW -eq 0 ]; then
                NOW=1
        fi

        local DURATION=$(($NOW-$START))
        local BYTES_PER_SECOND=$(( SIZE / DURATION ))
        local KBPS=$((SIZE/DURATION/1024))
        local MD5=$(md5sum $TMPFILE | awk '{ print $1 }')

        # This function prints out ugly stuff suitable for eval() 
        # rather than a pretty string. This makes it a bit more 
        # flexible if you have a custom format (or dare I say, locale?)

        printf "\nDURATION=%d\nBYTES=%d\nKBPS=%f\nMD5=%s\n" \
            $DURATION \
            $SIZE \
            $KBPS \
            $MD5
}

例子:

echo "wget"
wget -q -O /dev/stdout http://www.blah.com/somefile.zip | progress_filter

echo "tftp"
tftp -l /dev/stdout -g -r something/firmware.bin 192.168.1.1 | progress_filter

echo "scp"
scp [email protected]:~/myfile.tar /dev/stdout | progress_filter

I did a pure shell version for an embedded system taking advantage of:

  • /usr/bin/dd's SIGUSR1 signal handling feature.

    Basically, if you send a 'kill SIGUSR1 $(pid_of_running_dd_process)', it'll output
    a summary of throughput speed and amount transferred.

  • backgrounding dd and then querying it regularly for updates, and generating
    hash ticks like old-school ftp clients used to.

  • Using /dev/stdout as the destination for non-stdout friendly programs like scp

The end result allows you to take any file transfer operation and get progress update that looks like old-school FTP 'hash' output where you'd just get a hash mark for every X bytes.

This is hardly production quality code, but you get the idea. I think it's cute.

For what it's worth, the actual byte-count might not be reflected correctly in the number of hashes - you may have one more or less depending on rounding issues. Don't use this as part of a test script, it's just eye-candy. And, yes, I'm aware this is terribly inefficient - it's a shell script and I make no apologies for it.

Examples with wget, scp and tftp provided at the end. It should work with anything that has emits data. Make sure to use /dev/stdout for programs that aren't stdout friendly.

#!/bin/sh
#
# Copyright (C) Nathan Ramella ([email protected]) 2010 
# LGPLv2 license
# If you use this, send me an email to say thanks and let me know what your product
# is so I can tell all my friends I'm a big man on the internet!

progress_filter() {

        local START=$(date +"%s")
        local SIZE=1
        local DURATION=1
        local BLKSZ=51200
        local TMPFILE=/tmp/tmpfile
        local PROGRESS=/tmp/tftp.progress
        local BYTES_LAST_CYCLE=0
        local BYTES_THIS_CYCLE=0

        rm -f ${PROGRESS}

        dd bs=$BLKSZ of=${TMPFILE} 2>&1 \
                | grep --line-buffered -E '[[:digit:]]* bytes' \
                | awk '{ print $1 }' >> ${PROGRESS} &

        # Loop while the 'dd' exists. It would be 'more better' if we
        # actually looked for the specific child ID of the running 
        # process by identifying which child process it was. If someone
        # else is running dd, it will mess things up.

        # My PID handling is dumb, it assumes you only have one running dd on
        # the system, this should be fixed to just get the PID of the child
        # process from the shell.

        while [ $(pidof dd) -gt 1 ]; do

                # PROTIP: You can sleep partial seconds (at least on linux)
                sleep .5    

                # Force dd to update us on it's progress (which gets
                # redirected to $PROGRESS file.
                # 
                # dumb pid handling again
                pkill -USR1 dd

                local BYTES_THIS_CYCLE=$(tail -1 $PROGRESS)
                local XFER_BLKS=$(((BYTES_THIS_CYCLE-BYTES_LAST_CYCLE)/BLKSZ))

                # Don't print anything unless we've got 1 block or more.
                # This allows for stdin/stderr interactions to occur
                # without printing a hash erroneously.

                # Also makes it possible for you to background 'scp',
                # but still use the /dev/stdout trick _even_ if scp
                # (inevitably) asks for a password. 
                #
                # Fancy!

                if [ $XFER_BLKS -gt 0 ]; then
                        printf "#%0.s" $(seq 0 $XFER_BLKS)
                        BYTES_LAST_CYCLE=$BYTES_THIS_CYCLE
                fi
        done

        local SIZE=$(stat -c"%s" $TMPFILE)
        local NOW=$(date +"%s")

        if [ $NOW -eq 0 ]; then
                NOW=1
        fi

        local DURATION=$(($NOW-$START))
        local BYTES_PER_SECOND=$(( SIZE / DURATION ))
        local KBPS=$((SIZE/DURATION/1024))
        local MD5=$(md5sum $TMPFILE | awk '{ print $1 }')

        # This function prints out ugly stuff suitable for eval() 
        # rather than a pretty string. This makes it a bit more 
        # flexible if you have a custom format (or dare I say, locale?)

        printf "\nDURATION=%d\nBYTES=%d\nKBPS=%f\nMD5=%s\n" \
            $DURATION \
            $SIZE \
            $KBPS \
            $MD5
}

Examples:

echo "wget"
wget -q -O /dev/stdout http://www.blah.com/somefile.zip | progress_filter

echo "tftp"
tftp -l /dev/stdout -g -r something/firmware.bin 192.168.1.1 | progress_filter

echo "scp"
scp [email protected]:~/myfile.tar /dev/stdout | progress_filter
葬花如无物 2024-07-14 17:39:02

您可以通过覆盖一行来实现这一点。 使用 \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 提到如果您从长行开始然后想写短行,则会“失败”:在这种情况下,您需要覆盖长行的长度(例如,带空格)。

You can implement this by overwriting a line. Use \r to go back to the beginning of the line without writing \n to the terminal.

Write \n when you're done to advance the line.

Use echo -ne to:

  1. not print \n and
  2. to recognize escape sequences like \r.

Here's a demo:

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

In a comment below, puk mentions this "fails" if you start with a long line and then want to write a short line: In this case, you'll need to overwrite the length of the long line (e.g., with spaces).

病女 2024-07-14 17:39:02

您可能还对如何制作旋转器感兴趣:

我可以在 Bash 中制作旋转器吗?

当然!

<前><代码>i=1
sp="/-\|"
回显-n''
虽然是真的

printf "\b${sp:i++%${#sp}:1}"
完毕

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

\b 字符串被“退格”字符替换。 或者,
您可以使用 \r 返回到行的开头。

如果你想让它慢下来,在循环中放置一个睡眠命令
(在 printf 之后)。

POSIX 等效项是:

<前><代码>sp='/-\|'
打印''
虽然是真的; 做
printf '\b%.1s' "$sp"
sp=${sp#?}${sp%???}
完毕

如果你已经有一个可以做很多工作的循环,你可以调用
在每次迭代开始时使用以下函数来更新
微调器:

sp="/-\|" 
  SC=0 
  旋转() { 
     printf "\b${sp:sc++:1}" 
     ((sc==${#sp})) &&   SC=0 
  } 
  结束旋转(){ 
     printf "\r%s\n" "$@" 
  } 

  直到工作完成;   做 
     旋转 
     一些工作 ... 
  完毕 
  端旋 
  

You may also be interested in how to do a spinner:

Can I do a spinner in Bash?

Sure!

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

Each time the loop iterates, it displays the next character in the sp
string, wrapping around as it reaches the end. (i is the position of
the current character to display and ${#sp} is the length of the sp
string).

The \b string is replaced by a 'backspace' character. Alternatively,
you could play with \r to go back to the beginning of the line.

If you want it to slow down, put a sleep command inside the loop
(after the printf).

A POSIX equivalent would be:

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

If you already have a loop which does a lot of work, you can call the
following function at the beginning of each iteration to update the
spinner:

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
您的好友蓝忘机已上羡 2024-07-14 17:39:02

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

#!/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/

Got an easy progress bar function that i wrote the other day:

#!/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'

Or snag it from,
https://github.com/fearside/ProgressBar/

↘人皮目录ツ 2024-07-14 17:39:02

使用 Linux 命令 pv

它不知道它是否位于管道中间的大小,但它给出了速度和总数,从那里您可以计算出应该花费多长时间并获得反馈,以便您知道它没有挂起。

Use the Linux command pv.

It doesn't know the size if it's in the middle of the pipeline, but it gives a speed and total, and from there you can figure out how long it should take and get feedback so you know it hasn't hung.

别把无礼当个性 2024-07-14 17:39:02

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

预览

progress- bar.sh 正在运行

来源

我把它放在 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

I was looking for something more sexy than the selected answer, so did my own script.

Preview

progress-bar.sh in action

Source

I put it on 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
}

Usage

 progress-bar 100
独自唱情﹋歌 2024-07-14 17:39:02

一些帖子展示了如何显示命令的进度。 为了计算它,您需要查看您已经进步了多少。 在 BSD 系统上,某些命令(例如 dd(1))接受 SIGINFO 信号,并报告其进度。 在 Linux 系统上,某些命令的响应类似于 SIGUSR1。 如果此功能可用,您可以通过 _dd 管道输入以监视已处理的字节数。

或者,您可以使用 lsof 来获取文件的偏移量读取指针,从而计算进度。
以下是使用 lsof(1) 查看 wc(1) 读取名为 blob 的大文件的进度的示例。

$ wc -l blob &
[1] 3405769

$ lsof -w -o0 -o -c wc
COMMAND     PID USER   FD   TYPE  DEVICE       OFFSET     NODE NAME
[...]
wc      3405769  dds    3r   REG   254,7 0t2656059392  7733716 blob

我编写了一个名为 pmonitor 的命令,该命令显示处理指定的进程或文件。 使用它您可以执行以下操作。

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

Linux 和 FreeBSD shell 脚本的早期版本出现在我的博客(“Monitor Process Progress on Unix ”)。

Some posts have showed how to display the command's progress. In order to calculate it, you'll need to see how much you've progressed. On BSD systems some commands, such as dd(1), accept a SIGINFO signal, and will report their progress. On Linux systems some commands will respond similarly to SIGUSR1. If this facility is available, you can pipe your input through _dd to monitor the number of bytes processed.

Alternatively, you can use lsof to obtain the offset of the file's read pointer, and thereby calculate the progress.
Here is an example of using lsof(1) to see the progress of wc(1) reading a large file named blob.

$ wc -l blob &
[1] 3405769

$ lsof -w -o0 -o -c wc
COMMAND     PID USER   FD   TYPE  DEVICE       OFFSET     NODE NAME
[...]
wc      3405769  dds    3r   REG   254,7 0t2656059392  7733716 blob

I've written a command, named pmonitor, that displays the progress of processing a specified process or file. With it you can do things, such as the following.

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

An earlier version of Linux and FreeBSD shell scripts appears on my blog ("Monitor Process Progress on Unix").

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文