如何将进度条添加到shell脚本?

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

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

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

467487 次浏览
大多数unix命令不会给你这样的直接反馈,你可以这样做。 有些将在你可以使用的stdout或stderr上提供输出

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

据我所知,cp没有给出这种输出。要监视cp的进程,必须监视源文件和目标文件,并监视目标文件的大小。您可以使用stat (2)系统调用编写一个小的c程序来获取文件大小。这将读取源文件的大小,然后轮询目标文件,并根据到目前为止写入的文件的大小更新% complete条。

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

当你完成前进行时,写\n

使用echo -ne来:

  1. 不打印\n
  2. 来识别像\r这样的转义序列。

下面是一个演示:

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

在下面的评论中,puk提到,如果您以长行开始,然后想要编写短行,那么这种方法“失败”:在这种情况下,您将需要覆盖长行的长度(例如,使用空格)。

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

在我的系统上使用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

你可能也对如何做转盘感兴趣:

我能在Bash中做旋转吗?

当然!

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

每次循环迭代时,它显示sp . b0中的下一个字符 绳子,绕到末端。(i是位置 当前要显示的字符和${#sp}是sp的长度 字符串). < / p >

\b字符串被'backspace'字符取代。另外, 你可以使用\r返回到行开始。

如果你想让它慢下来,在循环中放一个睡眠命令 (printf之后).

POSIX的等价函数是:

sp='/-\|'
printf ' '
while true; do
printf '\b%.1s' "$sp"
sp=${sp#?}${sp%???}
done
如果你已经有一个做了很多工作的循环,你可以调用 函数在每次迭代开始时更新 微调控制项:< / p >
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

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

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

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

同样的效果可以通过:

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

我的解决方案显示焦油球的百分比 目前正在解压缩和写入。我用这个 当写入2GB根文件系统映像时。你真的 这些事情需要一个进度条。我所做的就是使用 gzip --list来获取未压缩的总大小 tarball。由此我计算出所需的阻塞因子 将文件分成100个部分。最后,我打印一个 每个块的检查点消息。对于一个2GB的文件 一个区块大约有10MB。如果太大了,你可以 将BLOCKING_FACTOR除以10或100,然后它是

假设您正在使用Bash,那么您可以使用 下面的shell函数

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%  \r' -zxf ${TARBALL}
}

使用Linux命令pv

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

要制作一个tar进度条

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

其中“19”是tar中的文件数除以预期进度条的长度。 例如:.tgz包含140个文件,你想要一个76 "."的进度条,你可以放-L 2.

你什么都不需要了。

我今天有同样的事情要做,根据Diomidis的答案,以下是我是如何做到的(linux debian 6.0.7)。 也许,这可以帮助你:

#!/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 %\r"
done

这可以让你看到命令仍在执行:

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循环. c。

有一次,我也有一个繁忙的脚本,它被占用了几个小时,没有任何进展。所以我实现了一个函数,主要包括前面的回答技巧:

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

这只适用于使用gnome zenity。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

首先,杆并不是唯一的管道进度仪表。另一个(可能更广为人知)是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,比如copy file1 file2,一个有用的技巧是使用进程替换:

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

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

更新:

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

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

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

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

  • /usr/bin/dd的SIGUSR1信号处理特性。

    基本上,如果您发送'kill SIGUSR1 $(pid_of_running_dd_process)',它将输出 吞吐量速度和传输量的摘要。李< / p > < / >
  • 后台dd,然后定期查询更新,并生成

  • .

    .
  • 使用/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

对我来说,到目前为止最容易使用和最好看的命令是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

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

#!/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 "\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/ < / p >

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

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

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;

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;

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循环中使用旗帜/变量来检查和显示进度的值/范围。

我使用了来自在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

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

只要输入一个从1到100的int值@stdin。举个简单而愚蠢的例子:

for a in {1..100}; do sleep .1s; echo $a| dialog --gauge "waiting" 7 30; done
我有这个/bin/Wait文件(chmod u+x perms)用于烹饪目的

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

我以“恐惧之边”提供的答案为基础

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

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

许多答案描述了编写自己的命令来打印'\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

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

如果你想让脚本的其他部分来控制进度条,内部的progressbar()函数本身也可以独立使用。例如,发送'progressbar 10 "正在创建目录树";'将显示:

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

当然,它会很迷幻……

#!/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\r"
};
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 "\n"
};
timeprogress "$1" "$2"

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

使用while循环频繁地监视进程的状态。

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

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

代码:

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

此“#”将打印直到睡眠终止,此方法用于实现进度条的进度时间程序。

你也可以用这种方法来命令shell脚本,以可视化的方式分析它的进程时间。

< >强错误: 这个pgrep方法并不是在所有情况下都有效,出乎意料的是另一个进程正在以相同的名称运行,while循环没有结束

因此通过指定PID来获取进程的运行状态,使用 可能进程可以用一些命令,

命令< em > p < / em >将列出所有带有id的进程,你需要grep来找出指定进程的pid

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

#!/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中的转轮。

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

预览

progress-bar.sh in action

我把它放在github progress-bar.sh

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 "\r"; }


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

使用

 progress-bar 100

没有看到任何类似的东西,这里所有的自定义函数似乎都只关注渲染,所以……下面是我非常简单的POSIX兼容解决方案,并逐级解释,因为这个问题并不简单。

博士TL;

渲染进度条非常简单。估计它应该渲染多少是另一回事。这是如何渲染(动画)进度条-你可以复制粘贴这个例子到一个文件并运行它:

#!/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 -打印到终端(即stdout)
  • echo -n -打印结尾没有新行
  • echo -e -在打印时解释特殊字符
  • "\r" -回车,返回行首的特殊字符

你可以让它以任何速度呈现任何内容,所以这种方法是非常通用的,例如经常用于“黑客”的可视化。在愚蠢的电影里,没有开玩笑。

完整答案(从0到工作示例)

问题的核心是如何确定$i值,即显示进度条的多少。在上面的例子中,我只是让它在for循环中递增来说明原理,但现实生活中的应用程序会使用无限循环,并在每次迭代中计算$i变量。要进行上述计算,需要以下成分:

  1. 有多少工作要做
  2. 到目前为止已经做了多少工作

对于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
  • foo=$(bar) -在子进程中运行bar,并将其stdout保存到$foo
  • stat -打印文件统计到stdout
  • stat -c -打印格式化的值
  • %s -总大小的格式

对于像文件解包这样的操作,计算源文件大小稍微困难一些,但仍然像获得未压缩文件的大小一样简单:

#!/bin/sh
src_size=$(gzip -l "$src" | tail -n1 | tr -s ' ' | cut -d' ' -f3)
  • gzip -l打印关于zip存档的信息
  • tail -n1 -从底部开始处理一行
  • tr -s ' ' -将多个空格转换为一个("挤压")
  • cut -d' ' -f3 -切割第三个空格分隔字段(列)

这是我之前提到的问题的核心。这个解越来越不通用了。实际进度的所有计算都与您试图可视化的域紧密相关,是单个文件操作、计时器倒计时、目录中文件数量的增加、对多个文件的操作等等,因此,它不能被重用。唯一可重用的部分是进度条渲染。为了重用它,你需要将它抽象并保存在一个文件中(例如/usr/lib/progress_bar.sh),然后定义函数来计算特定于你的域的输入值。这就是泛化代码的样子(我还使$BAR成为动态的,因为人们要求它,剩下的现在应该清楚了):

#!/bin/bash


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/bash


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

显然,你可以把它包装在一个函数中,重写以使用管道流,用$!获取fork进程ID并将其传递给progress_bar.sh,这样它就可以猜一猜如何计算要做的工作和已完成的工作,无论你想要什么。

一边笔记

我经常被问到这两件事:

  1. ${}:在上面的例子中,我使用了${foo:A:B}。这种语法的专业术语是参数扩展,它是一个内置的shell功能,允许操作变量(参数),例如用:修饰字符串,但也可以做其他事情——它不生成子shell。我能想到的最突出的参数展开描述(它不完全兼容POSIX,但让读者很好地理解这个概念)是在man bash页。
  2. $():在上面的例子中,我使用了foo=$(bar)。它在子进程(又名亚层)中生成一个单独的shell,在其中运行bar命令,并将其标准输出分配给$foo变量。它与进程替换不同,与 (|)完全不同。最重要的是,它有效。有人说应该避免这样做,因为这样做很慢。我认为这是“ok”;这里是因为无论这段代码试图可视化的内容持续了足够长的时间,以至于需要一个进度条。换句话说,子外壳并不是瓶颈。调用subshell也为我节省了解释为什么return不是大多数人认为的那样,foo=$(bar)0是什么,以及为什么从shell中传递函数的值通常不是shell函数所擅长的。为了找到更多关于它的信息,我再次强烈推荐man bash页面。

故障排除

如果你的shell实际上运行的是sh而不是bash,或者非常旧的bash,比如默认的osx,它可能会在echo -ne "\r${BAR:0:$i}"上阻塞。确切的错误是Bad substitution。如果这种情况发生在你身上,根据注释部分,你可以使用echo -ne "\r$(expr "x$name" : "x.\{0,$num_skip\}\(.\{0,$num_keep\}\)")"来做一个更可移植的posix兼容/可读性更差的子字符串匹配。

一个完整的/bin/sh示例:

#!/bin/sh


src=100
tgt=0


get_work_todo() {
echo $src
}


do_work() {
echo "$(( $1 + 1 ))"
}


BAR_length=50
BAR_character='#'
BAR=$(printf %${BAR_length}s | tr ' ' $BAR_character)
work_todo=$(get_work_todo)             # how much there is to do
work_done=0
while true; do
work_done="$(do_work $work_done)"
i=$(( $work_done * $BAR_length / $work_todo ))
n=$(( $BAR_length - $i ))
printf "\r$(expr "x$BAR" : "x.\{0,$n\}\(.\{0,$i\}\)")"
if [ $work_done = $work_todo ]; then
echo "\n"
break;
fi
sleep .1
done

基于Edouard Lopez的工作,我创建了一个适合屏幕大小的进度条。来看看。

enter image description here

它也发布在Git中心上。

#!/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 "\r"
done
printf "\n"
exit 0

享受

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

它通过使用一半的unicode块实现子字符精度

enter image description here

包括代码

#!/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}%) \r"
sleep 1
if [ $input -eq 10 ] ;then
echo -ne '\n'
fi


}

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

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

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

#!/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="\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 "$@"

这是它看起来的样子

上传文件

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

APT风格的进度条(不中断正常输出)

enter image description here

编辑:更新版本检查我的github页面

我对这个问题的回答不满意。我个人想要的是一个花哨的进度条,就像APT看到的那样。

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

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

请注意,该栏目前固定在100字符宽。如果你想把它缩放到终端的大小,这也很容易实现(我的github页面上的更新版本处理得很好)。

我将在这里张贴我的脚本。 使用例子:< / p >
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="\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 "\n"


# 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 "\n\n"
}


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}"
}
#!/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”设置为预计输出文件大小)

灵活的版本与随机的颜色,字符串操作和日期。

function spinner() {
local PID="$1"
local str="${2:-Processing!}"
local delay="0.1"
# tput civis  # hide cursor
while ( kill -0 $PID 2>/dev/null )
do
printf "\e[38;5;$((RANDOM%257))m%s\r\e[0m" "[$(date '+%d/%m/%Y %H:%M:%S')][ 🙉  🙊  🙈 $str 🙈  🙊  🙉 ]"; sleep "$delay"
printf "\e[38;5;$((RANDOM%257))m%s\r\e[0m" "[$(date '+%d/%m/%Y %H:%M:%S')][ 🙈  🙉  🙉 $str 🙊  🙉  🙈 ]"; sleep "$delay"
printf "\e[38;5;$((RANDOM%257))m%s\r\e[0m" "[$(date '+%d/%m/%Y %H:%M:%S')][ 🙊  🙈  🙊 $str 🙉  🙈  🙊 ]"; sleep "$delay"
done
printf "\e[38;5;$((RANDOM%257))m%s\r\e[0m" "[$(date '+%d/%m/%Y %H:%M:%S')][ ✅  ✅  ✅   Done!   ✅  ✅  ✅ ]"; sleep "$delay"
# tput cnorm  # restore cursor


return 0
}

用法:

# your long running proccess pushed to the background
sleep 20 &


# spinner capture-previous-proccess-id string
spinner $! 'Working!'

输出的例子:

[04/06/2020 03:22:24][ 🙊  🙈  🙊 Seeding! 🙉  🙈  🙊 ]

这可以通过一种相当简单的方式来实现:

  • 使用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重置自定义设置,否则即使进度条完成了,它也会影响进一步的输入。

自定义进度条

关于这个主题有很多不同的答案,但当计算文本文件操作的百分比时,使用current length / total size方式,例如显示ver_big_file.json进度的百分比,我建议为此使用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" | ...

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

我需要一个进度条来迭代csv文件中的行。能够将cprn的代码改编成对我有用的东西:

BAR='##############################'
FILL='------------------------------'
totalLines=$(wc -l $file | awk '{print $1}')  # 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%)

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

终端输出样本

test_progress_bar3.gif .gif .


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快速向桌面发送弹出气泡消息。这是必需的,因为音量级别可以在一秒钟内更改多次,并且默认的气泡消息行为是让消息在桌面上停留许多秒。

示例弹出气泡消息

tvpowered.gif .gif .

弹出气泡消息bash代码

从上面的脚本中,main函数被复制到一个名为tvpowered的现有bash脚本中名为VolumeBar的新函数。复制的main函数中的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 \

volume将弹出泡泡消息分组在一起,该组中的新消息立即替换前一个消息。你可以使用anything代替volume

雇佣(浮点)进度条

序言

对不起,这个不是那么短的答案。在这个答案中,我将使用< >强integer < / >强来呈现浮点数,< >强UTF-8 < / >强字体来更精细地呈现进度条,< >强parallelise < / >强另一个任务(sha1sum)来跟踪他的进度,所有这些都使用pure UTF-80以最小的资源占用来完成。

凤仙花属植物:请测试代码(复制/粘贴在一个新的终端窗口)在现在就开始吧!< / >强(中间),与

  • 最后一个动画演示(接近this.的末尾),
  • < >强实用示例< / >强 (at end)。

这里的所有演示都使用read -t <float seconds> && break而不是sleep。因此,所有循环都可以通过按返回键很好地停止。

简介

Yet Another Bash进度条

由于这里已经有很多答案,我想添加一些关于< >的强劲表现< / >强精度<强> < / >强的提示。

1. 避免叉!

因为进度条是在其他进程工作时运行的,所以这必须是< >强好< / >强进程…

因此,避免在不需要时使用< >强叉< / >强。例句:代替

mysmiley=$(printf '%b' \\U1F60E)

使用

printf -v mysmiley '%b' \\U1F60E

解释:当你运行var=$(command)时,你启动了一个新的进程来执行command,并在结束后将他的输出发送给变量$var。这是非常资源昂贵。请比较:

TIMEFORMAT="%R"
time for ((i=2500;i--;)){ mysmiley=$(printf '%b' \\U1F60E);}
2.292
time for ((i=2500;i--;)){ printf -v mysmiley '%b' \\U1F60E;}
0.017
bc -l <<<'2.292/.017'
134.82352941176470588235

在我的主机上,同样的分配$mysmiley的工作(只有2500次),使用比使用内置的printf -v似乎要慢135倍/更昂贵。

然后

echo $mysmiley
😎

所以你的function必须不打印(或输出)任何东西。你的函数必须将他的答案属性为< >强变量< / >强

2. 使用整数作为伪浮点数

下面是一个非常小且快速的函数,用于从整数中计算百分比,使用整数并回答一个伪浮点数:

percent(){
local p=00$(($1*100000/$2))
printf -v "$3" %.2f ${p::-3}.${p: -3}
}

用法:

# percent <integer to compare> <reference integer> <variable name>
percent 33333 50000 testvar
printf '%8s%%\n' "$testvar"
66.67%

3.使用UTF-8: ▏ ▎ ▍ ▌ ▋ ▊ ▉ █租用控制台图形

要使用bash渲染这些字符,您可以:

printf -v chars '\\U258%X ' {15..8}
printf "$chars\\n"
▏ ▎ ▍ ▌ ▋ ▊ ▉ █

然后我们必须使用8x string with作为graphic width

现在动手吧!

这个函数被命名为percentBar,因为它从提交的参数中呈现一个以百分比(浮动)为单位的条:

percentBar ()  {
local prct totlen=$((8*$2)) lastchar barstring blankstring;
printf -v prct %.2f "$1"
((prct=10#${prct/.}*totlen/10000, prct%8)) &&
printf -v lastchar '\\U258%X' $(( 16 - prct%8 )) ||
lastchar=''
printf -v barstring '%*s' $((prct/8)) ''
printf -v barstring '%b' "${barstring// /\\U2588}$lastchar"
printf -v blankstring '%*s' $(((totlen-prct)/8)) ''
printf -v "$3" '%s%s' "$barstring" "$blankstring"
}

用法:

# percentBar <float percent> <int string width> <variable name>
percentBar 42.42 $COLUMNS bar1
echo "$bar1"
█████████████████████████████████▉

显示细微差别:

percentBar 42.24 $COLUMNS bar2
printf "%s\n" "$bar1" "$bar2"
█████████████████████████████████▉
█████████████████████████████████▊

与颜色

由于渲染变量是一个固定宽度的字符串,使用颜色很容易:

percentBar 72.1 24 bar
printf 'Show this: \e[44;33;1m%s\e[0m at %s%%\n' "$bar" 72.1

Bar with color

小动画:

for i in {0..10000..33} 10000;do i=0$i
printf -v p %0.2f ${i::-2}.${i: -2}
percentBar $p $((COLUMNS-9)) bar
printf '\r|%s|%6.2f%%' "$bar" $p
read -srt .002 _ && break    # console sleep avoiding fork
done


|███████████████████████████████████████████████████████████████████████|100.00%


clear; for i in {0..10000..33} 10000;do i=0$i
printf -v p %0.2f ${i::-2}.${i: -2}
percentBar $p $((COLUMNS-7)) bar
printf '\r\e[47;30m%s\e[0m%6.2f%%' "$bar" $p
read -srt .002 _ && break
done

PercentBar animation

最后一个动画演示

另一个演示显示不同大小和颜色的输出:

printf '\n\n\n\n\n\n\n\n\e[8A\e7'&&for i in {0..9999..99} 10000;do
o=1 i=0$i;printf -v p %0.2f ${i::-2}.${i: -2}
for l in 1 2 3 5 8 13 20 40 $((COLUMNS-7));do
percentBar $p $l bar$((o++));done
[ "$p" = "100.00" ] && read -rst .8 _;printf \\e8
printf '%s\e[48;5;23;38;5;41m%s\e[0m%6.2f%%%b' 'In 1 char width: ' \
"$bar1" $p ,\\n 'with 2 chars: ' "$bar2" $p ,\\n 'or 3 chars: ' \
"$bar3" $p ,\\n 'in 5 characters: ' "$bar4" $p ,\\n 'in 8 chars: ' \
"$bar5" $p .\\n 'There are 13 chars: ' "$bar6" $p ,\\n '20 chars: '\
"$bar7" $p ,\\n 'then 40 chars' "$bar8" $p \
', or full width:\n' '' "$bar9" $p ''
((10#$i)) || read -st .5 _; read -st .1 _ && break
done

可以产生这样的结果:

Last animation percentBar animation

实用的GNU / Linux示例:带有进度条的sha1sum

在linux下,你可以在/proc伪文件系统下找到很多有用的信息,所以使用之前定义的函数percentBarpercent,下面是sha1progress:

percent(){ local p=00$(($1*100000/$2));printf -v "$3" %.2f ${p::-3}.${p: -3};}
sha1Progress() {
local -i totsize crtpos cols=$(tput cols) sha1in sha1pid
local sha1res percent prctbar
exec {sha1in}< <(exec sha1sum -b - <"$1")
sha1pid=$!
read -r totsize < <(stat -Lc %s "$1")
while ! read -ru $sha1in -t .025 sha1res _; do
read -r _ crtpos < /proc/$sha1pid/fdinfo/0
percent $crtpos $totsize percent
percentBar $percent $((cols-8)) prctbar
printf '\r\e[44;38;5;25m%s\e[0m%6.2f%%' "$prctbar" $percent;


done
printf "\r%s  %s\e[K\n" $sha1res "$1"
}

当然,25 ms超时意味着大约每秒刷新40次。这看起来可能有点过分,但在我的主机上运行正常,而且无论如何,这是可以调整的。

sha1Progress sample

解释:

  • 的输出创建一个新的文件描述符
  • <( ... ) fork任务在后台运行
  • sha1sum -b - <"$1"确保输入来自STDIN (fd/0)
  • while ! read -ru $sha1in -t .025 sha1res _当没有从子任务中读取输入时,在25 ms
  • /proc/$sha1pid/fdinfo/0内核变量,显示任务$sha1pid文件描述符0 (STDIN)信息