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

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

你建议用什么方法向shell脚本添加进度条?


当前回答

我需要一个进度条,将适合弹出气泡消息(通知-发送),以代表电视音量水平。最近我一直在用python写一个音乐播放器,而电视画面大部分时间都是关闭的。

终端输出样本


Bash脚本

#!/bin/bash

# Show a progress bar at step number $1 (from 0 to 100)


function is_int() { test "$@" -eq "$@" 2> /dev/null; } 

# Parameter 1 must be integer
if ! is_int "$1" ; then
   echo "Not an integer: ${1}"
   exit 1
fi

# Parameter 1 must be >= 0 and <= 100
if [ "$1" -ge 0 ] && [ "$1" -le 100 ]  2>/dev/null
then
    :
else
    echo bad volume: ${1}
    exit 1
fi

# Main function designed for quickly copying to another program 
Main () {

    Bar=""                      # Progress Bar / Volume level
    Len=25                      # Length of Progress Bar / Volume level
    Div=4                       # Divisor into Volume for # of blocks
    Fill="▒"                    # Fill up to $Len
    Arr=( "▉" "▎" "▌" "▊" )     # UTF-8 left blocks: 7/8, 1/4, 1/2, 3/4

    FullBlock=$((${1} / Div))   # Number of full blocks
    PartBlock=$((${1} % Div))   # Size of partial block (array index)

    while [[ $FullBlock -gt 0 ]]; do
        Bar="$Bar${Arr[0]}"     # Add 1 full block into Progress Bar
        (( FullBlock-- ))       # Decrement full blocks counter
    done

    # If remainder zero no partial block, else append character from array
    if [[ $PartBlock -gt 0 ]]; then
        Bar="$Bar${Arr[$PartBlock]}"
    fi

    while [[ "${#Bar}" -lt "$Len" ]]; do
        Bar="$Bar$Fill"         # Pad Progress Bar with fill character
    done

    echo Volume: "$1 $Bar"
    exit 0                      # Remove this line when copying into program
} # Main

Main "$@"

测试bash脚本

使用此脚本测试终端中的进度条。

#!/bin/bash

# test_progress_bar3

Main () {

    tput civis                              # Turn off cursor
    for ((i=0; i<=100; i++)); do
        CurrLevel=$(./progress_bar3 "$i")   # Generate progress bar 0 to 100
        echo -ne "$CurrLevel"\\r            # Reprint overtop same line
        sleep .04
    done
    echo -e \\n                             # Advance line to keep last progress
    echo "$0 Done"
    tput cnorm                              # Turn cursor back on
} # Main

Main "$@"

博士TL;

本节详细介绍如何使用notify-send将弹出气泡消息快速发送到桌面。这是必需的,因为音量级别可以在一秒钟内更改多次,并且默认的气泡消息行为是让消息在桌面上停留许多秒。

示例弹出气泡消息

弹出气泡消息bash代码

从上面的脚本中,主函数被复制到一个名为tvpowered的现有bash脚本中名为VolumeBar的新函数。已删除复制的主函数中的exit 0命令。

下面是如何调用它并让Ubuntu的notify-send命令知道我们将发送弹出泡泡消息:

VolumeBar $CurrVolume
# Ask Ubuntu: https://askubuntu.com/a/871207/307523
notify-send --urgency=critical "tvpowered" \
    -h string:x-canonical-private-synchronous:volume \
    --icon=/usr/share/icons/gnome/48x48/devices/audio-speakers.png \
    "Volume: $CurrVolume $Bar"

这是一个新的行,它告诉notify-send立即替换上一个弹出气泡:

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

卷将弹出气泡消息分组在一起,该组中的新消息立即替换前一个消息。你可以用任何东西来代替体积。

其他回答

一些帖子已经展示了如何显示命令的进度。为了计算它,你需要看看你已经进步了多少。在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脚本的早期版本出现在我的博客上(“在Unix上监视进程进度”)。

我使用了一个答案,从创建重复字符字符串在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 "#"

这意味着为bar准备50个“#”字符的字符串,在那之后:

progressbar 35 80

将显示“#”字符的数量,对应35/80的比例:

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

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

progressbar -1 80

较慢的版本都在一个函数中:

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

它可以被用作(和上面的例子一样):

progressbar 35 80 50

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

这是它看起来的样子

上传文件

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

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

来自Zenity进度条示例:

#!/bin/sh
(
echo "10" ; sleep 1
echo "# Updating mail logs" ; sleep 1
echo "20" ; sleep 1
echo "# Resetting cron jobs" ; sleep 1
echo "50" ; sleep 1
echo "This line will just be ignored" ; sleep 1
echo "75" ; sleep 1
echo "# Rebooting system" ; sleep 1
echo "100" ; sleep 1
) |
zenity --progress \
  --title="Update System Logs" \
  --text="Scanning mail logs..." \
  --percentage=0

if [ "$?" = -1 ] ; then
        zenity --error \
          --text="Update canceled."
fi

关于这个主题有很多不同的答案,但是当计算文本文件操作的百分比时,使用当前长度/总大小的方式,例如显示ver_big_file的百分比。我建议使用awk来实现这个目的,如下所示:

awk '
    function bar(x){s="";i=0;while (i++ < x) s=s "#";return s}
    BEGIN{
        ("ls -l " ARGV[1]) | getline total;
        split(total,array);
        total=array[5];
    }
    {
        cur+=length($0)+1;
        percent=int(cur / total * 100);
        printf "LINE %s:%s %s%%\r", NR, bar(percent*.8), percent 
    }
    END {print}' very_big_file.json | grep "keyword" | ...

这种方法非常精确,基于流,但只适用于文本文件。