bash 如何向shell脚本添加进度条?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/238073/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 17:46:05  来源:igfitidea点击:

How to add a progress bar to a shell script?

bashshellzsh

提问by Tom Feiner

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.

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

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

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

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

您建议通过哪些方式向 shell 脚本添加进度条?

回答by Mitch Haile

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

您可以通过覆盖一行来实现这一点。用于\r返回到行首而不写入\n终端。

Write \nwhen you're done to advance the line.

\n当你完成前进就行了。

Use echo -neto:

使用echo -ne于:

  1. not print \nand
  2. to recognize escape sequences like \r.
  1. 不打印\n
  2. 识别转义序列,如\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).

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

回答by Daenyth

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

您可能还对如何制作微调器感兴趣:

Can I do a spinner in Bash?

我可以在 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

当然!

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

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

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

如果您希望它慢下来,请在循环内(在 printf 之后)放置一个 sleep 命令。

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

回答by Diomidis Spinellis

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 SIGINFOsignal, 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 ddto monitor the number of bytes processed.

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

Alternatively, you can use lsofto obtain the offset of the file's read pointer, and thereby calculate the progress. 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.

或者,您可以使用lsof获取文件读取指针的偏移量,从而计算进度。我编写了一个名为pmonitor的命令,用于显示处理指定进程或文件的进度。有了它,您可以执行以下操作。

$ 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.

早期版本的 Linux 和 FreeBSD shell 脚本出现在我的博客上

回答by fearside

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

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

#!/bin/bash
# 1. Create ProgressBar function
# 1.1 Input is currentState() and totalState()
function ProgressBar {
# Process data
    let _progress=(*100/*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/

或者从https://github.com/fearside/ProgressBar/ 获取

回答by Seth Wegner

use the linux command pv:

使用 linux 命令 pv:

http://linux.die.net/man/1/pv

http://linux.die.net/man/1/pv

it doesn't know the size if it's in the middle of the stream, 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.

如果它在流的中间,它不知道大小,但它提供了速度和总数,从那里你可以计算出它需要多长时间并获得反馈,所以你知道它没有挂起。

回答by édouard Lopez

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

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

Preview

预览

progress-bar.sh in action

progress-bar.sh 在行动

Source

来源

I put it on github progress-bar.sh

我放在github上progress-bar.sh

progress-bar() {
  local duration=


    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

回答by Wojtek

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

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

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

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

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

The same effect may be obtained by:

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

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

回答by leebert

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

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

srcdir=
outfile=


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

回答by cprn

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.

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

TL;DR

TL; 博士

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 -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
  • {1..20}- 值从 1 到 20
  • echo -n- 在末尾不换行打印
  • echo -e- 打印时解释特殊字符
  • "\r"- 回车,一个特殊的字符返回到行首

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

完整答案

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

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

  1. how much work there is to be done
  2. how much work has been done so far
  1. 有多少工作要做
  2. 到目前为止完成了多少工作

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

如果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
  • stat- check file stats
  • -c- return formatted value
  • %s- total size
  • stat- 检查文件统计
  • -c- 返回格式化值
  • %s- 总尺寸

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- display info about zip archive
  • tail -n1- work with 1 line from the bottom
  • tr -s ' '- translate multiple spaces to one (squeeze them)
  • cut -d' ' -f3- cut 3rd space-delimited column
  • gzip -l- 显示有关 zip 存档的信息
  • tail -n1- 从底部开始使用 1 行
  • tr -s ' '- 将多个空格转换为一个(挤压它们)
  • cut -d' ' -f3- 剪切第三个空格分隔的列

Here's the meat of the problem, though. 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 $BARdynamic because people were asking for it, the rest should be clear by now):

不过,问题的关键就在这里。这个解决方案越来越不通用。实际进度的所有计算都与您尝试可视化的域紧密绑定,它是单个文件操作、计时器倒计时、目录中文件数量的增加、对多个文件的操作等,因此,它不能重复使用。唯一可重用的部分是进度条渲染。要重用它,您需要将其抽象化并保存在文件中(例如/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- a builtin for printing stuff in a given format
  • printf '%50s'- print nothing, pad it with 50 spaces
  • tr ' ' '#'- translate every space to hash sign
  • printf- 用于以给定格式打印内容的内置程序
  • printf '%50s'- 不打印,用 50 个空格填充
  • tr ' ' '#'- 将每个空格转换为哈希符号

And this is how you'd use it:

这就是你使用它的方式:

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

Obviously it can be wrapped in a function, rewritten to work with piped streams, rewritten to other language, whatever's your poison.

显然,它可以包装在一个函数中,重写为使用管道流,重写为其他语言,无论你的毒药是什么。

回答by nachoparker

I would also like to contribute my own progress bar

我也想贡献我自己的进度条

It achieves sub-character precision by using Half unicode blocks

它通过使用Half unicode 块来实现子字符精度

enter image description here

在此处输入图片说明

Code is included

包含代码