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

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

您建议用什么方法将进度条添加到shell脚本?

评论

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

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

这不应该移到unix.stackexchange.com吗?

我喜欢将pv用于任何可以通过管道传输的内容。示例:ssh remote“ cd / home / user / && tar czf-帐户” | pv -s 23091k |焦油xz

#1 楼

您可以通过覆盖一行来实现。使用\r返回到行的开头,而无需将\n写入终端。完成后,请写\n来推进行。

使用echo -ne可以:


不打印\n
以识别\r之类的转义序列。

这是一个演示:

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


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

评论


根据echo手册页(至少在MacOS X上是这样),sh / bash使用其自己的内置echo命令,该命令不接受“ -n” ...因此,要完成相同的操作,您需要输入\ r \ c在字符串的末尾,而不只是\ r

–贾斯汀·詹金斯(Justin Jenkins)
2012年4月2日在1:17



输出此内容的可移植方式是使用printf而不是echo。

–詹斯
2012年5月30日上午10:52

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

– Nurettin
2013年9月10日9:55



我不明白这一点-对于“我猜想此操作将花费多长时间未知硬件”黑客会接受并大量赞誉? pv是IMO的正确答案(但是bar也可以)

–斯蒂芬
2014年5月11日在22:31

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

–米奇·海尔(Mitch Haile)
2014年5月25日下午5:00

#2 楼

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

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


当然可以!

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


每次循环,它都会在sp
字符串中显示下一个字符,并在到达末尾时自动换行。 (i是要显示的当前字符的位置
,$ {#sp}是sp

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

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

POSIX等效项是:

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


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

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

until work_done; do
   spin
   some_work ...
done
endspin



评论


更短的版本,完全可移植*:while:;在/-\\ \ |;中执行s做printf“ \ r $ s”; sleep .1; done; done(*:sleep可能需要整数而不是小数)

–亚当·卡兹(Adam Katz)
15年8月19日在20:13

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

– goro
15年8月24日在15:33

@goro:在上述some_work ...行中;在此基础上,将进行更详细的讨论,并在此处找到Adam Katz的有用评论-着重于POSIX合规性。

–mklement0
16年7月9日在3:43



@AdamKatz:这是一个有用的,可移植的简化方法,但是为了匹配Daenyth的方法,微调器必须基于\ b而不是\ r,因为否则它将仅在一行的开头起作用:while:;为/-\\ \ |;中的c做做printf'%s \ b'“ $ c”;睡1;完成完成-或如果不希望在微调器后面显示光标:printf''&& while:;为/-\\ \ |;中的c做做printf'\ b%s'“ $ c”;睡1完成完成

–mklement0
16年7月9日在3:45



@kaushal – Ctrl + C将手动停止它。如果您有后台作业,则可以存储其PID(job = $!),然后在kill -0 $ job 2> / dev / null; do时运行,例如:sleep 15&job = $ !;而kill -0 $ job 2> / dev / null;为/-\\ \ |;中的s做做printf“ \ r $ s”;睡觉.1;完成完成

–亚当·卡兹(Adam Katz)
18-09-26在15:42



#3 楼

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

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


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

评论


您能解释一下1.2.1.1中的这一行吗?您是否正在使用_fill和_empty变量进行sed替换?我很困惑。

–Chirag
17年8月9日在13:25

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

–恐惧的一面
17年8月10日在16:31



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

–Chirag
17年8月10日在20:43

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

– enobufs
18年1月1日在23:00

甜。更改矩形的虚线使其具有更专业的外观:printf“ \ rProgress:[$ {_ fill // /▇} $ {_ empty // / /}] $ {_ progress} %%”

– Mehdi LAMRANI
19-11-27在1:44



#4 楼

一些帖子显示了如何显示命令的进度。为了计算它,您需要查看进度。在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

–锤子
14年7月29日在12:46

#5 楼

使用linux命令pv:

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

它不知道大小是否在中间流,但是它给出了速度和总数,您可以从中确定需要花费多长时间并获得反馈,以便您知道它没有挂起。

#6 楼

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

预览



来源

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


用法

 progress-bar 100


评论


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

– jan
18年6月1日在13:51

我认为用法应该是进度条100

– ji
18年7月3日在6:11

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

–不露面
18年8月4日在7:50



@faceless它不在您提供的时间范围之内,并且倒计时

–爱德华·洛佩兹(ÉdouardLopez)
18年9月13日在20:16

@Fusion这是一个Unicode字符(U + 2587下七位块),对于现代外壳应该是安全的。尝试一下您的环境

–爱德华·洛佩兹(ÉdouardLopez)
19年5月23日在14:56

#7 楼

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

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

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


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

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


评论


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

– Noam Manos
1月14日9:29

#8 楼

没见过类似的东西,这里的所有自定义功能似乎都集中在单独渲染上,因此...下面我的非常简单的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"-回车,一个特殊的char返回到行的开头。

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

完整答案

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


要完成多少工作
到目前为止已经完成了多少工作

如果是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


显然,它可以包装在一个函数中,可以重写为可使用管道流,改写为其他语言,这是您的毒药。

评论


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

– YCN-
4月20日14:05

@ YCN-我发现自己经常使用自己的实现,而不是我愿意承认的。谢啦! xD

–cprn
6月30日10:15

#9 楼

我还想贡献自己的进度条

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



包含代码

#10 楼

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

srcdir=
outfile=


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


#11 楼

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="3[s"
CODE_RESTORE_CURSOR="3[u"
CODE_CURSOR_IN_SCROLL_AREA="3[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 "3[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 "3[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=
    lines=$(tput lines)
    let lines=$lines
    # Save cursor
    echo -en "$CODE_SAVE_CURSOR"

    # Move cursor position to last row
    echo -en "3[${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 "3[${lines};0f"

    # clear progress bar
    tput el

    # Restore cursor position
    echo -en "$CODE_RESTORE_CURSOR"
}

function print_bar_text() {
    local percentage=

    # 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 [  -gt 99 ]
    then
        echo -ne "${progress_bar}"
    else
        echo -ne "${progress_bar}"
    fi
}

printf_new() {
    str=
    num=
    v=$(printf "%-${num}s" "$str")
    echo -ne "${v// /$str}"
}


#12 楼

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

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


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

评论


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

–世纪
15年4月24日在13:28

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

–鬣蜥
16-2-16在10:28

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

–Felipe
16年6月29日在6:17

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

–贝克斯
18年7月23日在19:08

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

–floydn
19年2月12日在17:37

#13 楼

看起来就是这样

上传文件

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


等待作业完成

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


实现它的简单函数

您只需将其复制粘贴到脚本中即可。不需要其他任何操作。

PROGRESS_BAR_WIDTH=50  # progress bar length in characters

draw_progress_bar() {
  # Arguments: current value, max value, unit of measurement (optional)
  local __value=
  local __max=
  local __unit=${3:-""}  # if unit is not supplied, do not display it

  # Calculate percentage
  if (( $__max < 1 )); then __max=1; fi  # anti zero division protection
  local __percentage=$(( 100 - ($__max*100 - $__value*100) / $__max ))

  # Rescale the bar according to the progress bar width
  local __num_bar=$(( $__percentage * $PROGRESS_BAR_WIDTH / 100 ))

  # Draw progress bar
  printf "["
  for b in $(seq 1 $__num_bar); do printf "#"; done
  for s in $(seq 1 $(( $PROGRESS_BAR_WIDTH - $__num_bar ))); do printf " "; done
  printf "] $__percentage%% ($__value / $__max $__unit)\r"
}


使用示例

这里,我们上传文件,并在每次迭代时重绘进度条。只要可以得到2个值(最大值和当前值),实际上执行什么作业都没有关系。在下面的示例中,最大值为file_size,并且当前值由某个函数提供称为uploaded_bytes

# Uploading a file
file_size=137921

while true; do
  # Get current value of uploaded bytes
  uploaded_bytes=$(some_function_that_reports_progress)

  # Draw a progress bar
  draw_progress_bar $uploaded_bytes $file_size "bytes"

  # Check if we reached 100%
  if [ $uploaded_bytes == $file_size ]; then break; fi
  sleep 1  # Wait before redrawing
done
# Go to the newline at the end of upload
printf "\n"


评论


整齐简单的功能。非常感谢!

– Andreas Kraft
18年3月3日,11:13

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

– Wajdi_jurry
5月29日14:20

#14 楼

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

对于tar这样的内容可以使用-v开关并将输出通过管道传递到程序,该程序会为其读取的每一行更新一个小动画。当tar写出文件列表时,它会被解散,程序可以更新动画。要完成一个百分比,您必须知道文件数量并计算行数。据我所知,cp不会提供这种输出。要监视cp的进度,您必须监视源文件和目标文件,并观察目标的大小。您可以使用stat(2)系统调用编写一个小的c程序来获取文件大小。这将读取源的大小,然后轮询目标文件,并根据迄今写入的文件大小更新完成百分比。

#15 楼

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

假设您正在使用Bash然后可以使用
以下shell函数

untar_progress () 
{ 
  TARBALL=
  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年1月23日在16:05

#16 楼

首先,酒吧不是唯一一个管道进度表。另一个(也许甚至更广为人知)是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通过此管道进行处理(在圆括号内),并且复制将其视为与普通文件一样(除了一个例外,它只能向前读取)。本身也允许复制。在man bar之后:

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


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

#17 楼

我更喜欢将对话框与--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 "" +%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 : "
    sleep 1s
    PTG=`echo "scale=0;$STEP * 100 / $DIFF"|bc -l`
    echo $PTG| dialog --title "$TITLE" --gauge "$TEXT" 7 72
done

if [ "" == "" ]; then msg="Espera terminada: ";audio="Listo";
else msg=;audio=;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"

#18 楼

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

但是,如果您的任何过程产生输出(例如7z a -r newZipFile myFolder会在压缩时输出每个文件名),则

安装python模块tqdm

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


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

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


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

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

评论


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

–cprn
19年7月28日在16:36

@cprn tqdm将STDERR的输入STDIN输送到STDOUT时显示STDERR的进度。在这种情况下,wc -l会收到与不包括tqdm相同的输入。

–casper.dcl
19年7月29日在18:31



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

–cprn
19年7月30日在5:33

#19 楼

根据爱德华·洛佩兹(Edouard Lopez)的工作,我创建了一个适合屏幕大小的进度条,无论它是什么。检查一下。



它也发布在Git Hub上。

#!/bin/bash
#
# Progress bar by Adriano Pinaffo
# Available at https://github.com/adriano-pinaffo/progressbar.sh
# Inspired on work by Edouard Lopez (https://github.com/edouard-lopez/progress-bar.sh)
# Version 1.0
# Date April, 28th 2017

function error {
  echo "Usage: q4312078q [SECONDS]"
  case  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
[[  =~ ^[0-9]+$ ]] || error 2

duration=
barsize=$((`tput cols` - 7))
unity=$(($barsize / $duration))
increment=$(($barsize%$duration))
skip=$(($duration/($duration-$increment)))
curr_bar=0
prev_bar=
for (( elapsed=1; elapsed<=$duration; elapsed++ ))
do
  # Elapsed
prev_bar=$curr_bar
  let curr_bar+=$unity
  [[ $increment -eq 0 ]] || {  
    [[ $skip -eq 1 ]] &&
      { [[ $(($elapsed%($duration/$increment))) -eq 0 ]] && let curr_bar++; } ||
    { [[ $(($elapsed%$skip)) -ne 0 ]] && let curr_bar++; }
  }
  [[ $elapsed -eq 1 && $increment -eq 1 && $skip -ne 1 ]] && let curr_bar++
  [[ $(($barsize-$curr_bar)) -eq 1 ]] && let curr_bar++
  [[ $curr_bar -lt $barsize ]] || curr_bar=$barsize
  for (( filled=0; filled<=$curr_bar; filled++ )); do
    printf "▇"
  done

  # Remaining
  for (( remain=$curr_bar; remain<$barsize; remain++ )); do
    printf " "
  done

  # Percentage
  printf "| %s%%" $(( ($elapsed*100)/$duration))

  # Return
  sleep 1
  printf "\r"
done
printf "\n"
exit 0


享受

#20 楼

对我来说,到目前为止,最容易使用和外观最好的是命令pvbar,就像有些人已经写过的

,例如:通常需要使用dd

备份整个驱动器您可以将dd if="$input_drive_path" of="$output_file_path"pv一起使用

您可以像这样:

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

这样进度就直接进入STDOUT

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


完成后会出现摘要

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


评论


您可以使用pv或bar可视化不同过程的进度吗,例如计时器倒数,在文本文件中的位置,您的应用安装,运行时设置等?

–cprn
19年7月28日在16:32

#21 楼

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

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;




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循环内使用标志/变量来检查和显示值/范围进展。

#22 楼

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

创建40%的进度:progreSh 40



#23 楼

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

使用for循环从0迭代到100
每步睡眠25毫秒(0.25秒)
附加到$bar变量的另一个=标记以使进度条变宽
回显进度条和百分比(\r清除行并返回到行的开头; -ne使echo不在末尾添加换行符并解析\r特殊字符)

function progress {
    bar=''
    for (( x=0; x <= 100; x++ )); do
        sleep 0.25
        bar="${bar}="
        echo -ne "$bar ${x}%\r"
    done
    echo -e "\n"
}

$ progress
> ========== 10% # here: after 2.5 seconds

$ progress
> ============================== 30% # here: after 7.5 seconds

彩色进度条
function progress {
    bar=''
    for (( x=0; x <= 100; x++ )); do
        sleep 0.05
        bar="${bar} "

        echo -ne "\r"
        echo -ne "\e[43m$bar\e[0m"

        local left="$(( 100 - $x ))"
        printf " %${left}s"
        echo -n "${x}%"
    done
    echo -e "\n"
}

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


#24 楼

我需要一个进度条来迭代csv文件中的行。能够将cprn的代码修改为对我有用的东西:
BAR='##############################'
FILL='------------------------------'
totalLines=$(wc -l $file | awk '{print }')  # 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%)


#25 楼

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


#26 楼

我使用了在shell脚本中创建重复字符字符串的答案来进行字符重复。对于需要显示进度条的脚本,我有两个相对较小的bash版本(例如,循环通过多个文件,但对大型tar文件或复制操作没有用)的情况。较快的一种包含两个功能,一个用于准备显示栏的字符串:

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


,另一个显示进度栏:

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


它可以用作:

preparebar 50 "#"


,这意味着为带有50个“#”字符的bar准备字符串,然后: br />
progressbar 35 80


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

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


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

progressbar -1 80


较慢的版本具有多个功能:

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


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

progressbar 35 80 50


如果需要stderr上的进度条,只需在以下位置添加>&2每个printf命令的末尾。

#27 楼

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

#!/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 [ "" == "done" ]; then
    spinner="X"
    percent_done="100"
    progress_message="Done!"
    new_line="\n"
  else
    spinner='/-\|'
    percent_done="${1:-0}"
    progress_message="$percent_done %"
  fi

  percent_none="$(( 100 - $percent_done ))"
  [ "$percent_done" -gt 0 ] && local done_bar="$(printf '#%.0s' $(seq -s ' ' 1 $percent_done))"
  [ "$percent_none" -gt 0 ] && local none_bar="$(printf '~%.0s' $(seq -s ' ' 1 $percent_none))"

  # print the progress bar to the screen
  printf "\r Progress: [%s%s] %s %s${new_line}" \
    "$done_bar" \
    "$none_bar" \
    "${spinner:x++%${#spinner}:1}" \
    "$progress_message"
}

main "$@"


评论


真好!为了使其正常工作,我必须将一行percent_none =“ $((100-” $ percent_done“))”更改为percent_none =“ $((100-$ percent_done))”“

–sergio郝海东冠状病六四事件法轮功
18年4月8日在18:06



#28 楼

我为嵌入式系统做了一个纯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  }' >> ${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  }')

        # 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是可怕的。

–user4401178
2015年12月1日15:09



尝试通过“#我的PID处理不正确”来解决这个问题

–synthesizerpatel
2015年12月3日,2:07

您也许可以捕获$!从dd开始,然后等待[[-e / proc / $ {DD_PID}]]。

–user4401178
2015年12月3日,下午2:26

#29 楼

如果必须显示时间进度条(通过事先知道显示时间),则可以按以下方式使用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秒。

#30 楼

我建立在fearside提供的答案上。

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

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

}

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'