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

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

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


当前回答

如果你必须显示一个临时进度条(通过提前知道显示时间),你可以像下面这样使用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秒。

其他回答

使用Linux命令pv。

如果它在管道中间,它不知道大小,但它给出了速度和总数,从那里你可以计算出它需要多长时间,并得到反馈,这样你就知道它没有挂起。

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

在我的系统上使用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
#!/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,你将在…2的标准输出(但您必须修改源代码,将“tot”设置为预计输出文件大小)

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

#!/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 ']\r'
}

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

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 $?
}

然后这样使用它:

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

它输出一个进度条,看起来像这样:

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

条形图随着输出行数达到目标而增长。如果行数超过了目标,条就重新开始(希望目标是好的)。

BTW:我在Mac OSX上使用bash。我基于这个代码从mariascio旋转。