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

How to add a progress bar to a shell script?

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

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

您建议如何向shell脚本添加进度条?


您可以通过重写一行来实现这一点。使用
返回行首,而不向终端写入

当你完成前进时,写下

echo -ne来:

  • 不打印
  • 识别像
    这样的转义序列。
  • 这是一个演示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    echo -ne '#####                     (33%)
    '

    sleep 1
    echo -ne '#############             (66%)
    '

    sleep 1
    echo -ne '#######################   (100%)
    '

    echo -ne '
    '

    在下面的注释中,puk提到,如果您从一条长线开始,然后想写一条短线,那么这个"失败":在这种情况下,您需要覆盖长线的长度(例如,用空格)。


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

    我能在巴什做个旋转器吗?

    Sure!

    1
    2
    3
    4
    5
    6
    7
    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
    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:

    1
    2
    3
    4
    5
    6
    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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    sp="/-\|"
    sc=0
    spin() {
       printf"\b${sp:sc++:1}"
       ((sc==${#sp})) && sc=0
    }
    endspin() {
       printf"
    %s
    "
    "$@"
    }

    until work_done; do
       spin
       some_work ...
    done
    endspin


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

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

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

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


    使用linux命令pv:

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

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


    前几天我写了一个简单的进度条函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    #!/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"
    Progress : [${_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 '
    Finished!
    '

    或者从……开始,
    https://github.com/fearside/progressbar网站/


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

    预览

    progress-bar.sh in action

    来源

    我把它放在Github progress-bar.sh

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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"
    "
    ; }

      for (( elapsed=1; elapsed<=$duration; elapsed++ )); do
          already_done; remaining; percentage
          sleep 1
          clean_line
      done
      clean_line
    }

    用法

    1
     progress-bar 100


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

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

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

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

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

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

    1
    2
    3
    4
    5
    srcdir=$1
    outfile=$2


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

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

    它使用半Unicode块实现子字符精度

    enter image description here

    包括代码


    这使您可以可视化命令仍在执行:

    1
    2
    3
    4
    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循环。


    没见过类似的东西,所以…我的简单解决方案:

    1
    2
    3
    4
    5
    6
    7
    #!/bin/bash
    BAR='####################'   # this is full bar, mine is 20 chars
    for i in {1..20}; do
        echo -ne"
    ${BAR:0:$i}"
    # print $i chars of $BAR from 0 position
        sleep .1
    done
    • echo -n打印,末尾无新行
    • echo -e—打印时解释特殊字符
    • "
      "
      回车,返回行首的特殊字符

    很久以前我在一个简单的"黑客视频"中使用它来模拟输入代码。;)


    我的解决方案显示了当前正在解压缩和写入。我用这个当写出2GB根文件系统映像时。你真的这些事情需要一个进度条。我所做的就是使用gzip --list得到塔尔鲍尔从中我计算出所需的阻塞因子把文件分成100个部分。最后,我打印了一个每个块的检查点消息。对于2GB文件,此一块大约10MB。如果太大的话你可以将阻塞因子除以10或100,然后很难按百分比打印出漂亮的输出。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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%  
    '
    -zxf ${TARBALL}
    }


    首先,酒吧不是唯一的一个管道进度表。另一个(也许更为人所知)是pv(管道查看器)。

    第二个例子是bar和pv:

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

    甚至:

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

    如果要在处理参数中给定文件(如copy file1 file2)的命令中使用bar和pv,一个有用的技巧是使用进程替换:

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

    进程替换是一个bash神奇的东西,它创建临时的fifo管道文件/dev/fd/,并通过这个管道从运行的进程(在括号内)连接stdout,copy看到它就像一个普通文件一样(只有一个例外,它只能向前读取)。

    更新:

    BAR命令本身也允许复制。男子酒吧后:

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

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


    大多数Unix命令都不会给您提供这样做的直接反馈。有些会在stdout或stderr上提供您可以使用的输出。

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

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


    我更喜欢使用对话框和--gauge参数。在许多发行版的.deb包安装和其他基本配置中非常常用。所以你不需要重新发明轮子…再一次

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

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

    我有这个/bin/wait文件(带有chmod u+x烫发)用于烹饪:p

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #!/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"


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

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

    安装python模块tqdm

    1
    2
    3
    4
    5
    $ 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。使用更多选项的示例:

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

    另外,您还可以使用tqdm将iterables包装为python代码。

    https://github.com/tqdm/tqdm/blob/master/readme.rst模块


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

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

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

    pv你可以这样做:

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

    这一进展直接归功于STDOUT,因为:

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

    完成后总结出来

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

    这只适用于使用gnome zenity。Zenity为bash脚本提供了强大的本地接口:https://help.gnome.org/users/zenity/stable/

    从Zenity进度条示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #!/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

    我在shell脚本中为char repeating创建了一个重复字符字符串,得到了一个答案。对于需要显示进度条的脚本,我有两个相对较小的bash版本(例如,一个循环遍历许多文件,但对于大tar文件或复制操作不有用)。更快的一个由两个函数组成,一个函数用于为条形图显示准备字符串:

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

    一个显示进度条:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    progressbar() {
    # $1 - number (-1 for clearing the bar)
    # $2 - max number
        if [ $1 -eq -1 ]; then
            printf"
      $barspaces
    "

        else
            barch=$(($1*barlen/$2))
            barsp=$((barlen-barch))
            printf"
    [%.${barch}s%.${barsp}s]
    "
    "$barchars""$barspaces"
        fi
    }

    它可以用作:

    1
    preparebar 50"#"

    这意味着准备带有50个""字符的条形图字符串,然后:

    1
    progressbar 35 80

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

    1
    [#####################                             ]

    请注意,在您(或其他程序)打印换行符之前,函数会反复在同一行上显示该条。如果将-1作为第一个参数,则该条将被删除:

    1
    progressbar -1 80

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    progressbar() {
    # $1 - number
    # $2 - max number
    # $3 - number of '#' characters
        if [ $1 -eq -1 ]; then
            printf"
      %*s
    "
    "$3"
        else
            i=$(($1*$3/$2))
            j=$(($3-i))
            printf"
    [%*s"
    "$i" | tr ' ' '#'
            printf"%*s]
    "
    "$j"
        fi
    }

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

    1
    progressbar 35 80 50

    如果在stderr上需要progressbar,只需在每个printf命令的末尾添加>&2


    apt样式进度条(不中断正常输出)

    enter image description here

    编辑:要获得更新版本,请检查我的GitHub页面

    我对这个问题的回答不满意。我个人想要的是一个奇特的进度条,正如APT所看到的。

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

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

    请注意,该条目前固定为100个字符宽。如果您希望将其扩展到终端的大小,这也相当容易完成(我的Github页面上的更新版本处理得很好)。

    我会把我的剧本贴在这里。使用实例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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上的版本):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    #!/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"
    "


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

    "

    }

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

    这是它的样子

    上传文件

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

    等待作业完成

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

    实现它的简单函数

    你可以复制粘贴到你的脚本。它不需要任何其他东西来工作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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)
    "

    }

    使用实例

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # 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"
    "


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

    1
    2
    3
    4
    5
    while true; do sleep 0.25 && echo -ne"
    \" && sleep 0.25 && echo -ne"

    |" && sleep 0.25 && echo -ne"
    /" && sleep 0.25 && echo -ne"
    -"; done;

    1
    2
    3
    4
    5
    while true; do sleep 0.25 && echo -ne"
    Activity: \" && sleep 0.25 && echo -ne"

    Activity: |" && sleep 0.25 && echo -ne"
    Activity: /" && sleep 0.25 && echo -ne"
    Activity: -"; done;

    1
    2
    3
    4
    5
    6
    while true; do sleep 0.25 && echo -ne"
    "
    && sleep 0.25 && echo -ne"
    >"
    && sleep 0.25 && echo -ne"
    >>"
    && sleep 0.25 && echo -ne"
    >>>"
    ; sleep 0.25 && echo -ne"
    >>>>"
    ; done;

    1
    2
    3
    4
    5
    6
    7
    while true; do sleep .25 && echo -ne"
    :Active:"
    && sleep .25 && echo -ne"
    :aCtive:"
    && sleep .25 && echo -ne"
    :acTive:"
    && sleep .25 && echo -ne"
    :actIve:"
    && sleep .25 && echo -ne"
    :actiVe:"
    && sleep .25 && echo -ne"
    :activE:"
    ; done;

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


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    #!/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="
    "

      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"
     Progress: [%s%s] %s %s${new_line}"
    \
       "$done_bar" \
       "$none_bar" \
       "${spinner:x++%${#spinner}:1}" \
       "$progress_message"
    }

    main"$@"


    基于EdouardLopez的工作,我创建了一个适合屏幕大小的进度条,不管它是什么。过来看。

    enter image description here

    它也发布在Git Hub上。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    #!/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"
    "

    done
    printf"
    "

    exit 0

    享受


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #!/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;

    输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    0 %
    12 %
    25 %
    37 %
    50 %
    62 %
    75 %
    87 %
    100 %

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


    我为一个嵌入式系统做了一个纯粹的shell版本,利用:

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

      基本上,如果发送一个"kill sigusr1$(pid_running_dd_process)",它将输出吞吐量速度和传输量的摘要。

    • 回溯DD,然后定期查询更新,并生成哈希滴答声就像以前的FTP客户端那样。

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

    最终的结果允许您执行任何文件传输操作,并获取类似于老式ftp"hash"输出的进度更新,在这里您只需为每个x字节获取一个hash标记。

    这不是生产质量代码,但你知道。我觉得很可爱。

    就其价值而言,实际字节数可能无法正确反映在哈希数中——根据舍入问题的不同,您可能或多或少有一个。不要把它作为测试脚本的一部分,它只是一个眼糖。而且,是的,我知道这是非常低效的——这是一个shell脚本,我不为此道歉。

    最后提供了wget、scp和tftp示例。它应该可以处理任何已经发出数据的东西。确保对不支持stdout的程序使用/dev/stdout。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    #!/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"
    DURATION=%d
    BYTES=%d
    KBPS=%f
    MD5=%s
    "
    \
                $DURATION \
                $SIZE \
                $KBPS \
                $MD5
    }

    实例:

    1
    2
    3
    4
    5
    6
    7
    8
    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


    我建立在Fearside提供的答案之上

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    #!/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"
    Progress : [${_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 '
    Finished!
    '

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

    创造40%的进步:progreSh 40

    enter image description here


    如果您需要显示一个时间进度条(通过提前知道显示时间),您可以使用python,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #!/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('
    '
    )
        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脚本的进度条:

    1
    python progressbar.py 10 50

    它将显示一个进度条大小的50字符,并"运行"10秒。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    #!/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}%)
    "
       
        sleep 1
        if [ $input -eq 10 ] ;then
            echo -ne '
    '

        fi

    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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

    创建tar进度条

    1
    tar xzvf pippo.tgz |xargs -L 19 |xargs -I@ echo -n"."

    其中"19"是tar中的文件数除以预期进度条的长度。示例:.tgz包含140个文件,您需要一个76"."的进度条,您可以放置-l 2。

    你不需要别的了。


    我想根据命令输出的行数和上一次运行的目标行数来跟踪进度:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    #!/bin/bash
    function lines {
      local file=$1
      local default=$2
      if [[ -f $file ]]; then
        wc -l $file | awk '{print $1}';
      else
        echo $default
      fi
    }

    function bar {
      local items=$1
      local total=$2
      local size=$3
      percent=$(($items*$size/$total % $size))
      left=$(($size-$percent))
      chars=$(local s=$(printf"%${percent}s"); echo"${s// /=}")
      echo -ne"[$chars>";
      printf"%${left}s"
      echo -ne ']
    '

    }

    function clearbar {
      local size=$1
      printf" %${size}s "
      echo -ne"
    "

    }

    function progress {
      local pid=$1
      local total=$2
      local file=$3

      bar 0 100 50
      while [["$(ps a | awk '{print $1}' | grep $pid)" ]]; do
        bar $(lines $file 0) $total 50
        sleep 1
      done
      clearbar 50
      wait $pid
      return $?
    }

    然后这样使用:

    1
    2
    3
    target=$(lines build.log 1000)
    (mvn clean install > build.log 2>&1) &
    progress $! $target build.log

    它输出的进度条如下所示:

    1
    [===============================================>   ]

    当输出的行数达到目标值时,条形图就会增大。如果行数超过了目标值,那么条线就会重新开始(希望目标值是好的)。

    顺便说一句:我在mac osx上使用bash。我根据Mariascio的旋转器编写的代码。


    首先将进程执行到后台,然后经常观察它的运行状态,即正在运行的打印模式,然后再次检查它的状态是否正在运行;

    使用while循环经常监视进程的状态。

    使用pgrep或任何其他命令来监视和获取进程的运行状态。

    如果使用pgrep,则根据需要将不必要的输出重定向到/dev/null。

    代码:

    1
    2
    sleep 12&
    while pgrep sleep &> /dev/null;do echo -en"#";sleep 0.5;done

    此""将一直打印到睡眠终止,此方法用于实现程序的进度条。

    您还可以将此方法用于命令shell脚本,以便以可视方式分析IT处理时间。

    缺陷:此pgrep方法不适用于所有情况,意外的是另一个进程正在以相同的名称运行,while循环没有结束。

    因此,通过指定进程的PID来获取进程的运行状态,使用希望这个过程能与一些命令一起使用,

    命令ps a将列出ID为的所有进程,您需要grep来找出指定进程的pid。


    这是nexace为bash脚本编写的迷幻进度条。它可以从命令行调用为"./progressbar x y",其中"x"是以秒为单位的时间,"y"是与该进度部分关联的消息。

    如果您希望脚本的其他部分控制ProgressBar,那么内部progressBar()函数本身独立性也很好。例如,发送"ProgressBar 10"创建目录树";"将显示:

    1
    [#######                                     ] (10%) Creating directory tree

    当然,这会很好地迷幻…

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    #!/bin/bash

    if ["$#" -eq 0 ]; then echo"x is "time in seconds" and z is "message""; echo"Usage: progressbar x z"; exit; fi
    progressbar() {
            local loca=$1; local loca2=$2;
            declare -a bgcolors; declare -a fgcolors;
            for i in {40..46} {100..106}; do
                    bgcolors+=("$i")
            done
            for i in {30..36} {90..96}; do
                    fgcolors+=("$i")
            done
            local u=$(( 50 - loca ));
            local y; local t;
            local z; z=$(printf '%*s'"$u");
            local w=$(( loca * 2 ));
            local bouncer=".oO°Oo.";
            for ((i=0;i<loca;i++)); do
                    t="${bouncer:((i%${#bouncer})):1}"
                    bgcolor="\\E[${bgcolors[RANDOM % 14]}m \\033[m"
                    y+="$bgcolor";
            done
            fgcolor="\\E[${fgcolors[RANDOM % 14]}m"
            echo -ne" $fgcolor$t$y$z$fgcolor$t \\E[96m(\\E[36m$w%\\E[96m)\\E[92m $fgcolor$loca2\\033[m
    "

    };
    timeprogress() {
            local loca="$1"; local loca2="$2";
            loca=$(bc -l <<< scale=2\;"$loca/50")
            for i in {1..50}; do
                    progressbar"$i""$loca2";
                    sleep"$loca";
            done
            printf"
    "

    };
    timeprogress"$1""$2"


    有一次我也有一个繁忙的脚本,占用了好几个小时却没有显示任何进展。因此,我实现了一个功能,主要包括前面答案的技术:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    #!/bin/bash
    # Updates the progress bar
    # Parameters: 1. Percentage value
    update_progress_bar()
    {
      if [ $# -eq 1 ];
      then
        if [[ $1 == [0-9]* ]];
        then
          if [ $1 -ge 0 ];
          then
            if [ $1 -le 100 ];
            then
              local val=$1
              local max=100

              echo -n"["

              for j in $(seq $max);
              do
                if [ $j -lt $val ];
                then
                  echo -n"="
                else
                  if [ $j -eq $max ];
                  then
                    echo -n"]"
                  else
                    echo -n"."
                  fi
                fi
              done

              echo -ne""$val"%
    "


              if [ $val -eq $max ];
              then
                echo""
              fi
            fi
          fi
        fi
      fi
    }

    update_progress_bar 0
    # Further (time intensive) actions and progress bar updates
    update_progress_bar 100


    我今天也有同样的事情要做,基于diomidis的答案,我是这样做的(linux debian 6.0.7)。也许,这可以帮助你:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #!/bin/bash

    echo"getting script inode"
    inode=`ls -i ./script.sh | cut -d"" -f1`
    echo $inode

    echo"getting the script size"
    size=`cat script.sh | wc -c`
    echo $size

    echo"executing script"
    ./script.sh &
    pid=$!
    echo"child pid = $pid"

    while true; do
            let offset=`lsof -o0 -o -p $pid | grep $inode | awk -F"" '{print $7}' | cut -d"t" -f 2`
            let percent=100*$offset/$size
            echo -ne" $percent %
    "

    done