如何将进度条添加到Shell脚本?


413

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

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

您建议采用什么方式将进度条添加到Shell脚本?


另请参阅stackoverflow.com/questions/12498304/…,以获取控制逻辑的示例(后台执行作业并执行某些操作直到完成)。
2015年

1
在编写脚本时,我们经常发现一些有用的要求。日志记录,显示进度,颜色,奇特的输出等...我一直觉得应该有某种简单的脚本框架。最终,由于找不到任何东西,我决定实施一个。您可能会发现这很有帮助。这是纯bash,我的意思是Just Bash。github.com/SumuduLansakara/JustBash
Sumudu

这不应该移到unix.stackexchange.com吗?
伊桑(Ethan)

Answers:


685

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


23
根据echo手册页(至少在MacOS X上是这样),sh / bash使用它们自己的内置echo命令,该命令不接受“ -n” ...因此,要完成相同的操作,您需要输入\ r \ c,而不是只是\ r
贾斯汀·詹金斯

51
输出此内容的可移植方式是使用printf代替echo
詹斯2012年

9
对于printf,我们必须使用以下格式:printf "#### (50%%)\r",它不能与单引号一起使用,并且需要转义百分号。
nurettin

7
我不明白这一点-对于“我猜想此操作将花费多长时间来处理未知硬件”黑客,大家都接受了很多赞誉?PV是IMO的正确答案(但酒吧也可以这样做)
2014年

19
问题是“如何制作进度条”,其中包含复制文件的示例。我关注的是“图形”问题,而不是文件复制操作的距离计算。
米奇·海尔

73

您可能也对如何做微调框感兴趣:

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

当然!

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

15
更短的版本,完全可移植*:while :;do for s in / - \\ \|; do printf "\r$s";sleep .1;done;done(*:sleep可能需要整数而不是小数)
Adam Katz 2015年

1
@丹妮丝 谢谢。请使用前面的代码在哪里调用需要观察的命令?
goro

@goro:在some_work ...上面的行中;重点是符合POSIX - -一个是基于这样一种有用的答案和亚当·卡茨乐于助人的评论更详细的讨论,可以发现在这里
mklement0 '16

@AdamKatz:这是一个有用的,可移植的简化方法,但是为了匹配Daenyth的方法,微调器必须基于\b而不是\r,因为它只能在一行的开始处起作用:while :; do for c in / - \\ \|; do printf '%s\b' "$c"; sleep 1; done; done-或,如果在微调器后面显示光标是不希望的:printf ' ' && while :; do for c in / - \\ \|; do printf '\b%s' "$c"; sleep 1; done; done
mklement0 '16

1
@kaushal – Ctrl + C将手动将其停止。如果您有后台作业,则可以存储其PID(job=$!),然后运行while kill -0 $job 2>/dev/null;do …,例如:sleep 15 & job=$!; while kill -0 $job 2>/dev/null; do for s in / - \\ \|; do printf "\r$s"; sleep .1; done; done
Adam Katz

48

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

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

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

我的博客中显示了Linux和FreeBSD Shell脚本的早期版本。


这太棒了,我总是忘了通过pv传送内容:-)我认为我的“ stat”命令的工作方式略有不同,我的脚本的Linux版本:gist.github.com/unhammer/b0ab6a6aa8e1eeaf236b
不知所措,

很棒的帖子,awk在玩的时候总是喜欢它!

这很棒!感谢您的出色脚本!
Thebeagle '16

47

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

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

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

}

# Variables
_start=1

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

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

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


您能解释一下1.2.1.1中的这一行吗?您是否正在使用_fill和_empty变量进行sed替换?我糊涂了。
希拉格

我不使用sed,而是使用bash内部的“子字符串替换”,因为这是一件容易的事,所以我更喜欢使用bash的内部功能进行此类工作。代码看起来也更好。:-)在此处检查tldp.org/LDP/abs/html/string-manipulation.html并搜索子字符串替换。
fearside

$ {_ fill}被分配为$ {_ done}个空格。这很漂亮。干得好。我肯定会在所有bash脚本中使用它哈哈
Chirag,

很棒的工作@fearside!当_progress的值与上一个值没有变化时,我做了一些略微的调整以提高速度。github.com/enobufs/bash-tools/blob/master/bin/progbar
enobufs

甜。更改矩形的虚线会使其具有更专业的外观:printf "\rProgress : [${_fill// /▇}${_empty// / }] ${_progress}%%"
Mehdi LAMRANI '19年


31

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

预习

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

1
我不知道如何将其集成到某些未知过程的过程中。如果我的进程较早完成(例如,用于解压缩文件),如何停止进度栏。

我认为用法应该是progress-bar 100
jirarium

确实是有吸引力的进步。它如何与通过ssh处理远程服务器上长时间动作的功能绑定在一起?我的意思是,如何衡量(例如)远程服务器上的升级时间?
不露面的

1
@faceless它不是在这个代码的范围您提供的时间,它倒计时
爱德华·洛佩兹

1
@Fusion这是一个Unicode字符(U + 2587下七位块),对于现代外壳来说应该是安全的。试试看您的环境
爱德华·洛佩兹(

18

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

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

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

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

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

+1是最简单的方法!如果看不到任何打印点,请尝试减少数量,例如--checkpoint=.10。使用提取时,它也很好用tar -xz
Noam Manos

13

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

srcdir=$1
outfile=$2


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

13

没看到类似的东西,这里的所有自定义功能似乎都集中在单独渲染上,因此...下面我非常简单的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 -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
  • stat -检查文件统计
  • -c -返回格式化值
  • %s -总大小

对于诸如文件解压缩之类的操作,计算源大小会稍微困难一些,但仍然与获取未压缩文件的大小一样容易:

#!/bin/sh
src_size=$(gzip -l "$src" | tail -n1 | tr -s ' ' | cut -d' ' -f3)
  • gzip -l -显示有关zip存档的信息
  • tail -n1 -从底部开始处理1行
  • tr -s ' ' -将多个空格转换为一个(压缩)
  • cut -d' ' -f3 -切割第三个以空格分隔的列

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

#!/bin/sh

BAR_length=50
BAR_character='#'
BAR=$(printf "%${BAR_length}s" | tr ' ' $BAR_character)

work_todo=$(get_work_todo)             # how much there is to do

while true; do
    work_done=$(get_work_done)         # how much has been done so far
    i=$(( $work_done * $BAR_length / $work_todo ))
    echo -ne "\r${BAR:0:$i}"
    if [ $work_done == $work_todo ]; then
        echo ""
        break;
    fi
    sleep .1
done
  • printf -用于以给定格式打印内容的内置函数
  • printf '%50s' -不打印任何内容,用50个空格填充
  • tr ' ' '#' -将每个空格转换为哈希符号

这就是您将如何使用它:

#!/bin/sh

src=/path/to/source/file
tgt=/path/to/target/file

function get_work_todo() {
    echo $(stat -c%s "$src")
}

function get_work_done() {
    [ -e "$tgt" ] &&                   # if target file exists
        echo $(stat -c%s "$tgt") ||    # echo its size, else
        echo 0                         # echo zero
}

cp "$src" "$tgt" &                     # copy in the background

source /usr/lib/progress_bar.sh        # execute the progress bar

显然,它可以包装在一个函数中,可以重写以与管道流一起工作,可以重写为其他语言,而这无济于事。


1
对于那些想要最简单的东西的人,我只是用cprn作为第一个答案。这是一个非常简单的函数进度条,它使用一些愚蠢的比例规则来绘制
进度


9

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

7

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

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循环,该循环在后台执行并回显 “”。每一秒。这将显示.在外壳中。运行tar命令或所需的任何命令。当该命令执行完毕后,请杀死在后台运行的最后一个作业-这是无限的while循环


在执行过程中,另一个作业不能在后台启动并且有可能被杀死而不是被进度循环杀死吗?
Centimane 2015年

我认为您可以将其放在脚本中,因此这只会捕获该脚本的退出。
Iguananaut

1
我喜欢这个命令,我正在文件中使用它。我有点不安,因为我不太了解它是如何工作的。第一和第三行更容易理解,但我仍然不确定。我知道这是一个古老的答案,但是有没有办法让我对编程新手有不同的解释
Felipe

1
这是唯一的真实答案,其他答案只是脚本101玩具进度条,什么也没有,也没有用在真实的,一次性的,不可跟踪的(几乎所有)程序上。谢谢。
bekce

@Felipe,while循环是一个后台进程。$!在第一个陷阱中,它将捕获该后台进程的进程ID,并确保如果当前/父进程结束,则后台进程也将死亡,并且不会被挂起。当您的一个或多个长命令结束时,kill语句将结束后台进程。
floydn

7

这是它的外观

上载档案

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

整齐简单的功能。非常感谢!
安德烈亚斯·卡夫

这就是我要寻找的!非常感谢:)
wajdi_jurry

4

大多数Unix命令不会给您直接反馈,您可以从中进行此操作。有些会在您可以使用的stdout或stderr上提供输出。

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

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


4

我的解决方案显示了当前未压缩和写入的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}
}

不错的解决方案,但是当您要压缩目录时该怎么做?
Samir Sadek '18

4

首先,酒吧不是唯一一个管道进度表。另一个(也许甚至更广为人知)是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,那么一个有用的技巧是使用进程替换

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

进程替换是一个bash魔术,它创建临时的fifo管道文件/ dev / fd /并通过该管道从运行的进程(在圆括号内)连接stdout,并且复制将其视为与普通文件一样(除了一个例外,它只能读取它)转发)。

更新:

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

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

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


3

我更喜欢将对话框--gauge参数一起使用。在.deb软件包安装和许多发行版的其他基本配置中经常使用。因此,您无需重新发明轮子...

只需将int值设置为1到100 @stdin。一个简单而愚蠢的例子:

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

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

#!/bin/bash
INIT=`/bin/date +%s`
NOW=$INIT
FUTURE=`/bin/date -d "$1" +%s`
[ $FUTURE -a $FUTURE -eq $FUTURE ] || exit
DIFF=`echo "$FUTURE - $INIT"|bc -l`

while [ $INIT -le $FUTURE -a $NOW -lt $FUTURE ]; do
    NOW=`/bin/date +%s`
    STEP=`echo "$NOW - $INIT"|bc -l`
    SLEFT=`echo "$FUTURE - $NOW"|bc -l`
    MLEFT=`echo "scale=2;$SLEFT/60"|bc -l`
    TEXT="$SLEFT seconds left ($MLEFT minutes)";
    TITLE="Waiting $1: $2"
    sleep 1s
    PTG=`echo "scale=0;$STEP * 100 / $DIFF"|bc -l`
    echo $PTG| dialog --title "$TITLE" --gauge "$TEXT" 7 72
done

if [ "$2" == "" ]; then msg="Espera terminada: $1";audio="Listo";
else msg=$2;audio=$2;fi 

/usr/bin/notify-send --icon=stock_appointment-reminder-excl "$msg"
espeak -v spanish "$audio"

所以我可以说:

Wait "34 min" "warm up the oven"

要么

Wait "dec 31" "happy new year"


2

对我来说,到目前为止,最容易使用和看上去最好的是命令,pv或者bar像已经写过的家伙一样

例如:需要使用以下命令备份整个驱动器 dd

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

pv您可以使它像这样:

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

进度直接STDOUT是这样的:

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

完成后总结出来

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

您是否可以使用pvbar可视化不同进程的进度,例如计时器倒数,文本文件中的位置,您的应用安装,运行时设置等?
cprn

2

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


我认为您的“更多选项”示例不起作用。似乎是通过管道将tqdmSTDOUT 传递给wc -l了。您可能想逃避那个。
cprn

1
@cprn在tqdmSTDERR其输入STDIN传递到时将显示进度STDOUT。在这种情况下,wc -l谁将收到与tqdm未包括在内相同的输入。
casper.dcl

啊,现在有意义。感谢您的解释。
cprn

2

根据爱德华·洛佩兹(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

请享用



1

这仅适用于gnome zenity。Zenity为bash脚本提供了一个很棒的本机界面:https ://help.gnome.org/users/zenity/stable/

从Zenity Progress Bar示例中:

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

1

我使用了在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个“#”字符的bar准备字符串,然后:

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命令的末尾添加即可。


1

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

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

要么

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

要么

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

要么

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

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


1

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

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

1
真好!为了使其正常工作,我不得不将线路更改percent_none="$(( 100 - "$percent_done" ))"percent_none="$(( 100 - $percent_done))"
sergio '18

0

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

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

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

  • 为dd设置背景,然后定期查询更新,并像过去的ftp客户端一样生成哈希值。

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

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

这几乎不是生产质量代码,但是您可以理解。我觉得很可爱

就其价值而言,实际的字节数可能无法正确反映在哈希数中-根据舍入问题,您可能会有一个或多个字节。不要将其用作测试脚本的一部分,这只是让人眼花can乱。而且,是的,我知道这是非常低效的-这是一个Shell脚本,对此我不表示歉意。

最后提供了wget,scp和tftp的示例。它应该与任何发出数据的东西一起工作。确保对不是stdout友好的程序使用/ dev / stdout。

#!/bin/sh
#
# Copyright (C) Nathan Ramella (nar+progress-script@remix.net) 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 user@192.168.1.1:~/myfile.tar /dev/stdout | progress_filter

不错的主意,只要您提前准备好文件大小,就可以用这种方式提供比PV更高的价值,但是盲目地发出信号pidof dd是令人恐惧的。

试图用“#我的PID处理不正确”来指出这一点
synthesizerpatel

你或许可以捕获$!dd伺候[[ -e /proc/${DD_PID} ]]

0

如果必须显示时间进度条(通过提前知道显示时间),则可以使用Python,如下所示:

#!/bin/python
from time import sleep
import sys

if len(sys.argv) != 3:
    print "Usage:", sys.argv[0], "<total_time>", "<progressbar_size>"
    exit()

TOTTIME=float(sys.argv[1])
BARSIZE=float(sys.argv[2])

PERCRATE=100.0/TOTTIME
BARRATE=BARSIZE/TOTTIME

for i in range(int(TOTTIME)+1):
    sys.stdout.write('\r')
    s = "[%-"+str(int(BARSIZE))+"s] %d%% "
    sys.stdout.write(s % ('='*int(BARRATE*i), int(PERCRATE*i)))
    sys.stdout.flush()
    SLEEPTIME = 1.0
    if i == int(TOTTIME): SLEEPTIME = 0.1
    sleep(SLEEPTIME)
print ""

然后,假设您将Python脚本另存为progressbar.py,则可以通过运行以下命令来显示bash脚本中的进度条:

python progressbar.py 10 50

它会显示一个进度条大小的50字符并“运行” 10几秒钟。


0

我建立在fearside提供的答案上

这将连接到Oracle数据库以检索RMAN还原的进度。

#!/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}%%"

}

function rman_check {
sqlplus -s / as sysdba <<EOF
set heading off
set feedback off
select
round((sofar/totalwork) * 100,0) pct_done
from
v\$session_longops
where
totalwork > sofar
AND
opname NOT LIKE '%aggregate%'
AND
opname like 'RMAN%';
exit
EOF
}

# Variables
_start=1

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

_rman_progress=$(rman_check)
#echo ${_rman_progress}

# Proof of concept
#for number in $(seq ${_start} ${_end})

while [ ${_rman_progress} -lt 100 ]
do

for number in _rman_progress
do
sleep 10
ProgressBar ${number} ${_end}
done

_rman_progress=$(rman_check)

done
printf '\nFinished!\n'

0
#!/bin/bash

function progress_bar() {
    bar=""
    total=10
    [[ -z $1 ]] && input=0 || input=${1}
    x="##"
   for i in `seq 1 10`; do
        if [ $i -le $input ] ;then
            bar=$bar$x
        else
            bar="$bar  "
       fi
    done
    #pct=$((200*$input/$total % 2 + 100*$input/$total))
    pct=$(($input*10))
    echo -ne "Progress : [ ${bar} ] (${pct}%) \r"    
    sleep 1
    if [ $input -eq 10 ] ;then
        echo -ne '\n'
    fi

}

可以创建一个以1-10的比例绘制条数的函数:

progress_bar 1
echo "doing something ..."
progress_bar 2
echo "doing something ..."
progress_bar 3
echo "doing something ..."
progress_bar 8
echo "doing something ..."
progress_bar 10

0
#!/bin/bash
tot=$(wc -c /proc/$$/fd/255 | awk '/ /{print $1}')
now() {
echo $(( 100* ($(awk '/^pos:/{print $2}' < /proc/$$/fdinfo/255)-166) / (tot-166) )) "%"
}
now;
now;
now;
now;
now;
now;
now;
now;
now;

输出:

0 %
12 %
25 %
37 %
50 %
62 %
75 %
87 %
100 %

注意:如果您将输入1而不是255而不是255,则将监视标准输入...将标准输出2输出(但是您必须修改源以将“ tot”设置为预计的输出文件大小)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.