当在bash或*NIX中的任何其他shell中编写脚本时,在运行需要超过几秒钟时间的命令时,需要一个进度条。
例如,复制一个大文件,打开一个大tar文件。
你建议用什么方法向shell脚本添加进度条?
当在bash或*NIX中的任何其他shell中编写脚本时,在运行需要超过几秒钟时间的命令时,需要一个进度条。
例如,复制一个大文件,打开一个大tar文件。
你建议用什么方法向shell脚本添加进度条?
当前回答
我为嵌入式系统做了一个纯shell版本,利用了:
/usr/bin/dd的SIGUSR1信号处理特性。 基本上,如果您发送'kill SIGUSR1 $(pid_of_running_dd_process)',它将输出 吞吐量速度和传输量的摘要。 后台dd,然后定期查询它的更新,并生成 像老式的FTP客户端一样。 使用/dev/stdout作为非stdout友好程序(如scp)的目的地
最终的结果允许你进行任何文件传输操作,并获得进度更新,看起来像老式的FTP“哈希”输出,在那里你只需要为每个X字节获得一个哈希标记。
这几乎不是产品质量代码,但您可以理解。我觉得很可爱。
不管怎样,实际的字节计数可能不会正确地反映在哈希数中——根据舍入问题,可能会多一个或少一个。不要将它用作测试脚本的一部分,它只是花瓶。而且,是的,我知道这是非常低效的——这是一个shell脚本,我不为此道歉。
最后提供了使用wget、scp和tftp的示例。它应该与任何发出数据的东西一起工作。确保对标准输出不友好的程序使用/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
其他回答
这可以让你看到命令仍在执行:
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循环。
有一次,我也有一个繁忙的脚本,它被占用了几个小时,没有任何进展。所以我实现了一个函数,主要包括前面的回答技巧:
#!/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"%\r"
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
大多数unix命令不会为您提供可以执行此操作的直接反馈。 有些将在您可以使用的stdout或stderr上提供输出。
对于tar之类的东西,您可以使用-v开关并将输出管道到一个程序,该程序为读取的每一行更新一个小动画。当tar写出一个文件列表时,程序可以更新动画。要计算完成百分比,您必须知道文件的数量并计算行数。
据我所知,cp没有给出这种输出。要监视cp的进程,必须监视源文件和目标文件,并监视目标文件的大小。你可以写一个小的c程序,使用stat(2)系统调用来获取文件大小。这将读取源文件的大小,然后轮询目标文件,并根据到目前为止写入的文件的大小更新% complete条。
如果你必须显示一个临时进度条(通过提前知道显示时间),你可以像下面这样使用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秒。
您可以通过重写一行来实现这一点。使用\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提到,如果您以长行开始,然后想要编写短行,那么这种方法“失败”:在这种情况下,您将需要覆盖长行的长度(例如,使用空格)。