在bash或* NIX中的任何其他Shell中编写脚本时,在运行将花费几秒钟的命令的同时,需要一个进度条。
例如,复制一个大文件,打开一个大tar文件。
您建议采用什么方式将进度条添加到Shell脚本?
在bash或* NIX中的任何其他Shell中编写脚本时,在运行将花费几秒钟的命令的同时,需要一个进度条。
例如,复制一个大文件,打开一个大tar文件。
您建议采用什么方式将进度条添加到Shell脚本?
Answers:
您可以通过覆盖一行来实现。用于\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提到“失败”,如果您从长行开始然后想写一条短行:在这种情况下,您将需要覆盖长行的长度(例如,带空格)。
printf
代替echo
。
printf "#### (50%%)\r"
,它不能与单引号一起使用,并且需要转义百分号。
您可能也对如何做微调框感兴趣:
当然!
i=1 sp="/-\|" echo -n ' ' while true do printf "\b${sp:i++%${#sp}:1}" done
每次循环迭代时,它都会在sp字符串中显示下一个字符,并在到达末尾时回绕。(i是要显示的当前字符的位置,$ {#sp}是sp字符串的长度)。
\ b字符串将替换为“退格”字符。或者,您可以使用\ r来返回到行的开头。
如果您希望它变慢,请在循环内(在printf之后)放置一个sleep命令。
POSIX等效项为:
sp='/-\|' printf ' ' while true; do printf '\b%.1s' "$sp" sp=${sp#?}${sp%???} done
如果您已经有一个执行大量工作的循环,则可以在每次迭代的开始时调用以下函数来更新微调器:
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
while :;do for s in / - \\ \|; do printf "\r$s";sleep .1;done;done
(*:sleep
可能需要整数而不是小数)
some_work ...
上面的行中;重点是符合POSIX - -一个是基于这样一种有用的答案和亚当·卡茨乐于助人的评论更详细的讨论,可以发现在这里。
\b
而不是\r
,因为它只能在一行的开始处起作用:while :; do for c in / - \\ \|; do printf '%s\b' "$c"; sleep 1; done; done
-或,如果在微调器后面显示光标是不希望的:printf ' ' && while :; do for c in / - \\ \|; do printf '\b%s' "$c"; sleep 1; done; done
job=$!
),然后运行while kill -0 $job 2>/dev/null;do …
,例如:sleep 15 & job=$!; while kill -0 $job 2>/dev/null; do for s in / - \\ \|; do printf "\r$s"; sleep .1; done; done
一些帖子显示了如何显示命令的进度。为了计算它,您需要查看进度。在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脚本的早期版本。
awk
在玩的时候总是喜欢它!
得到了我前几天写的一个简单的进度栏功能:
#!/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'
printf "\rProgress : [${_fill// /▇}${_empty// / }] ${_progress}%%"
使用linux命令pv:
它不知道大小是否在流的中间,但是它给出了速度和总数,您可以从中找出需要花费多长时间并获得反馈,以便知道它没有挂起。
我一直在寻找比所选答案更性感的东西,所以我自己的脚本也是如此。
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
progress-bar 100
GNU tar有一个有用的选项,它提供了简单进度条的功能。
(...)另一个可用的检查点操作是“点”(或“。”)。它指示tar在标准列表流上打印一个点,例如:
$ tar -c --checkpoint=1000 --checkpoint-action=dot /var
...
可以通过以下方法获得相同的效果:
$ tar -c --checkpoint=.1000 /var
--checkpoint=.10
。使用提取时,它也很好用tar -xz
。
没看到类似的东西,这里的所有自定义功能似乎都集中在单独渲染上,因此...下面我非常简单的POSIX兼容解决方案,并提供逐步说明,因为这个问题并不简单。
渲染进度条非常容易。估计应该渲染多少是另一回事。这是渲染(动画)进度条的方法-您可以将此示例复制并粘贴到文件中并运行它:
#!/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到20echo -n
-打印结束时没有换行echo -e
-在打印时解释特殊字符"\r"
-回车,特殊字符返回到行首您可以使它以任何速度呈现任何内容,因此此方法非常通用,例如,通常用于可视化傻电影中的“黑客攻击”,而不会在开玩笑。
问题的关键在于如何确定该$i
值,即要显示多少进度条。在上面的示例中,我只是让它递增for
循环来说明原理,但是实际应用中将使用无限循环并$i
在每次迭代时计算变量。要进行上述计算,需要以下要素:
如果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
stat
-检查文件统计-c
-返回格式化值%s
-总大小对于诸如文件解压缩之类的操作,计算源大小会稍微困难一些,但仍然与获取未压缩文件的大小一样容易:
#!/bin/sh
src_size=$(gzip -l "$src" | tail -n1 | tr -s ' ' | cut -d' ' -f3)
gzip -l
-显示有关zip存档的信息tail -n1
-从底部开始处理1行tr -s ' '
-将多个空格转换为一个(压缩)cut -d' ' -f3
-切割第三个以空格分隔的列不过,这是问题的症结所在。这种解决方案越来越少。实际进度的所有计算都与您要显示的域紧密相关,它是单个文件操作,计时器倒数,目录中文件数量的增加,对多个文件的操作等,因此,它不能重复使用。唯一可重复使用的部分是进度条渲染。要重用它,您需要对其进行抽象并保存在文件中(例如/usr/lib/progress_bar.sh
),然后定义用于计算特定于您域的输入值的函数。这就是通用代码的样子(我也做了$BAR
动态修改,因为人们一直在要求它,其余的应该现在就弄清楚了):
#!/bin/sh
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/sh
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
显然,它可以包装在一个函数中,可以重写以与管道流一起工作,可以重写为其他语言,而这无济于事。
编辑:有关更新的版本,请检查我的github页面
我对这个问题的答复不满意。我个人正在寻找的是APT看到的花哨的进度条。
我看了APT的C源代码,并决定为bash编写自己的等效代码。
该进度条将很好地停留在终端的底部,并且不会干扰发送到终端的任何输出。
请注意,该栏目前固定为100个字符宽。如果您想将其缩放到终端的大小,这也很容易实现(我的github页面上的更新版本可以很好地解决此问题)。
我将在这里发布我的脚本。用法示例:
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}"
}
这使您可以看到命令仍在执行:
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循环。
上载档案
[##################################################] 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"
大多数Unix命令不会给您直接反馈,您可以从中进行此操作。有些会在您可以使用的stdout或stderr上提供输出。
对于诸如tar之类的东西,您可以使用-v开关并将输出通过管道传输到程序,该程序会针对读取的每一行更新一个小动画。当tar写出文件列表时,它会被解散,程序可以更新动画。要完成百分比,您将必须知道文件数量并计算行数。
就我所知,cp不会提供此类输出。要监视cp的进度,您必须监视源文件和目标文件,并观察目标的大小。您可以使用stat(2)系统调用编写一个小的c程序来获取文件大小。这将读取源的大小,然后轮询目标文件,并根据迄今写入的文件大小更新完成百分比。
我的解决方案显示了当前未压缩和写入的tarball的百分比。写出2GB根文件系统映像时,会用到它。您确实需要这些东西的进度条。我要做的是使用
gzip --list
获得压缩包的总未压缩大小。据此,我计算出将文件分为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}
}
首先,酒吧不是唯一一个管道进度表。另一个(也许甚至更广为人知)是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 <(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程序。
我更喜欢将对话框与--gauge参数一起使用。在.deb软件包安装和许多发行版的其他基本配置中经常使用。因此,您无需重新发明轮子...
只需将int值设置为1到100 @stdin。一个简单而愚蠢的例子:
for a in {1..100}; do sleep .1s; echo $a| dialog --gauge "waiting" 7 30; done
我有这个/ bin / Wait文件(带有chmod u + x权限)用于烹饪:P
#!/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"
对我来说,到目前为止,最容易使用和看上去最好的是命令,pv
或者bar
像已经写过的家伙一样
例如:需要使用以下命令备份整个驱动器 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
pv
或bar
可视化不同进程的进度,例如计时器倒数,文本文件中的位置,您的应用安装,运行时设置等?
许多答案描述了编写自己的打印命令'\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代码包装可迭代对象。
tqdm
STDOUT 传递给wc -l
了。您可能想逃避那个。
tqdm
将STDERR
其输入STDIN
传递到时将显示进度STDOUT
。在这种情况下,wc -l
谁将收到与tqdm
未包括在内相同的输入。
根据爱德华·洛佩兹(Edouard Lopez)的工作,我创建了一个适合屏幕大小的进度条,无论它是什么。一探究竟。
它也发布在Git Hub上。
#!/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
请享用
这仅适用于gnome zenity。Zenity为bash脚本提供了一个很棒的本机界面:https ://help.gnome.org/users/zenity/stable/
从Zenity Progress Bar示例中:
#!/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脚本中创建重复字符字符串的答案来进行字符重复。对于需要显示进度条的脚本,我有两个相对较小的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 "#"
这意味着为带有50个“#”字符的bar准备字符串,然后:
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上的进度条,只需>&2
在每个printf命令的末尾添加即可。
要指示活动进度,请尝试以下命令:
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循环中使用标志/变量来检查和显示进度的值/范围。
使用上面列出的建议,我决定实现自己的进度栏。
#!/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 "$@"
percent_none="$(( 100 - "$percent_done" ))"
为percent_none="$(( 100 - $percent_done))"
我利用以下优势为嵌入式系统制作了纯shell版本:
/ usr / bin / dd的SIGUSR1信号处理功能。
基本上,如果您发送“ kill SIGUSR1 $(pid_of_running_dd_process)”,它将输出吞吐速度和转移量的摘要。
为dd设置背景,然后定期查询更新,并像过去的ftp客户端一样生成哈希值。
使用/ dev / stdout作为非stdout友好程序(如scp)的目标
最终结果使您可以进行任何文件传输操作,并获得进度更新,看起来就像老式的FTP“哈希”输出,在该输出中,您只需要为每个X字节获取一个哈希标记。
这几乎不是生产质量代码,但是您可以理解。我觉得很可爱
就其价值而言,实际的字节数可能无法正确反映在哈希数中-根据舍入问题,您可能会有一个或多个字节。不要将其用作测试脚本的一部分,这只是让人眼花can乱。而且,是的,我知道这是非常低效的-这是一个Shell脚本,对此我不表示歉意。
最后提供了wget,scp和tftp的示例。它应该与任何发出数据的东西一起工作。确保对不是stdout友好的程序使用/ 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
如果必须显示时间进度条(通过提前知道显示时间),则可以使用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
几秒钟。
我建立在fearside提供的答案上
这将连接到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'
#!/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的比例绘制条数的函数:
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
#!/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而不是255,则将监视标准输入...将标准输出2输出(但是您必须修改源以将“ tot”设置为预计的输出文件大小)