*NIX の bash またはその他のシェルでスクリプトを作成する場合、コマンドの実行中に数秒以上かかる場合は、進行状況バーが必要です。
たとえば、大きなファイルをコピーしたり、大きな tar ファイルを開いたりします。
プログレス バーをシェル スクリプトに追加するには、どのような方法をお勧めしますか?
これは、行を上書きすることで実装できます。端末\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 は、長い行から始めて短い行を書きたい場合、これが「失敗する」と述べています。この場合、長い行の長さを (たとえば、スペースで) 上書きする必要があります。
スピナーのやり方にも興味があるかもしれません:
もちろん!
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
Linux コマンドを使用しpv
ます。
パイプラインの途中にある場合、サイズはわかりませんが、速度と合計が得られます。そこから、所要時間を計算してフィードバックを取得できるため、ハングしていないことがわかります。
いくつかの投稿では、コマンドの進行状況を表示する方法が示されています。それを計算するには、どれだけ進んだかを確認する必要があります。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 シェル スクリプトの以前のバージョンは、私のブログ("Monitor Process Progress on Unix") に掲載されています。
選択した回答よりもセクシーなものを探していたので、自分のスクリプトもそうしました。
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 準拠のソリューションと段階的な説明を以下に示します。
プログレス バーのレンダリングは非常に簡単です。どのくらいレンダリングする必要があるかを見積もることは別の問題です。プログレス バーをレンダリング (アニメーション化) する方法は次のとおりです。この例をコピーしてファイルに貼り付けて実行できます。
#!/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"
- 改行、行頭に戻るための特別な文字任意のコンテンツを任意の速度でレンダリングできるため、この方法は非常に普遍的です。たとえば、ばかげた映画の「ハッキング」の視覚化によく使用されますが、冗談ではありません。
問題の本質は、値を決定する$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
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
- 下から1行で作業tr -s ' '
- 複数のスペースを 1 つに変換します (「スクイーズ」します)。cut -d' ' -f3
- スペースで区切られた 3 番目のフィールド (列) をカットこれが私が以前に言及した問題の本質です。このソリューションは、ますます一般的ではなくなります。実際の進行状況のすべての計算は、視覚化しようとしているドメインに密接に関連付けられています。それは、単一のファイル操作、タイマーのカウントダウン、ディレクトリ内のファイル数の増加、複数のファイルに対する操作などです。再利用できません。唯一の再利用可能な部分は、プログレス バーのレンダリングです。それを再利用するには、それを抽象化してファイル (例: /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
明らかに、これを関数にラップし、パイプされたストリームで動作するように書き直し、フォークされたプロセス ID を取得し$!
て渡すことprogress_bar.sh
ができます。これにより、実行する作業と完了した作業を計算する方法を推測できます。
よく聞かれるのは次の 2 点です。
${}
: 上記の例では、 を使用しています${foo:A:B}
。この構文の専門用語はParameter Expansionです。これは、変数 (パラメーター) を操作できる組み込みのシェル機能です。たとえば、文字列をトリミングする:
だけでなく、他のことも実行できます。サブシェルは生成されません。私が考えることができるパラメーター展開の最も顕著な説明 (完全には POSIX 互換ではありませんが、読者が概念をよく理解できるようにするもの) は、man bash
ページにあります。$()
: 上記の例では、 を使用していますfoo=$(bar)
。サブプロセス (別名Subshell ) で別のシェルを生成し、bar
その中でコマンドを実行し、その標準出力を$foo
変数に割り当てます。これはプロセス置換と同じではなく、パイプ( )とはまったく異なるもの|
です。最も重要なことは、それが機能することです。これは遅いので避けるべきだと言う人もいます。このコードが視覚化しようとしているものは何でも、進行状況バーが必要なほど長く続くため、これはここでは「問題ありません」と主張します。つまり、サブシェルはボトルネックではありません。サブシェルを呼び出すと、終了ステータスreturn
とは何か、ほとんどの人が考えているものではない理由を説明する手間も省けますまた、シェルの関数から値を渡すことが、シェル関数が一般的に得意とすることではない理由。そのすべてについて詳しく知るには、このページを強くお勧めしman bash
ます。GNU tarには、単純なプログレス バーの機能を提供する便利なオプションがあります。
(...) もう 1 つの使用可能なチェックポイント アクションは、「ドット」(または「.」) です。これは tar に、標準のリスト ストリームに 1 つのドットを出力するように指示します。たとえば、次のようになります。
$ tar -c --checkpoint=1000 --checkpoint-action=dot /var
...
以下の方法でも同じ効果が得られます。
$ tar -c --checkpoint=.1000 /var
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
ほとんどの UNIX コマンドは、これを実行できるような直接的なフィードバックを提供しません。使用できる stdout または stderr に出力を提供するものもあります。
tar のようなものでは、-v スイッチを使用して出力をプログラムにパイプし、読み取った行ごとに小さなアニメーションを更新することができます。tar がファイルのリストを書き出すと、プログラムはアニメーションを更新できます。完了率を計算するには、ファイルの数を知り、行数を数えなければなりません。
私が知る限り、cp はこの種の出力を提供しません。cp の進行状況を監視するには、ソース ファイルと宛先ファイルを監視し、宛先のサイズを監視する必要があります。ファイル サイズを取得するstat (2)システム コールを使用して小さな C プログラムを作成できます。これにより、ソースのサイズが読み取られ、宛先ファイルがポーリングされ、現在までに書き込まれたファイルのサイズに基づいて % 完了バーが更新されます。
私のソリューションは、現在圧縮解除されて書き込まれているtarballのパーセンテージを表示します。これは、2GBのルートファイルシステムイメージを書き出すときに使用します。あなたは本当にこれらのもののためのプログレスバーが必要です。私がしている
gzip --list
のは、tarballの非圧縮サイズの合計を取得するために使用することです。それから、ファイルを100の部分に分割するために必要なブロッキング係数を計算します。最後に、各ブロックのチェックポイントメッセージを出力します。2GBのファイルの場合、これにより約10MBのブロックが得られます。それが大きすぎる場合は、BLOCKING_FACTORを10または100で割ることができますが、パーセンテージできれいな出力を出力するのは難しくなります。
Bashを使用していると仮定すると、次のシェル関数を使用できます
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}
}
--gaugeパラメータを指定してダイアログを使用することを好みます。多くのディストリビューションの .deb パッケージのインストールやその他の基本的な構成で非常に頻繁に使用されます。したがって、車輪を再発明する必要はありません...もう一度
1 から 100 までの int 値を @stdin に入れるだけです。1 つの基本的でばかげた例:
for a in {1..100}; do sleep .1s; echo $a| dialog --gauge "waiting" 7 30; done
私はこの/bin/Waitファイル (chmod u+x perms を使用) を調理用に持っています :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"
これは、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
一時的な進行状況バーを表示する必要がある場合 (表示時間を事前に知っておくことで)、次のように 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'
これは、nExace による bash スクリプト用のサイケデリックなプログレス バーです。コマンドラインから「./progressbar x y」として呼び出すことができます。ここで、「x」は秒単位の時間で、「y」は進行状況のその部分に関連付けられたメッセージです。
スクリプトの他の部分でプログレスバーを制御したい場合は、内部の progressbar() 関数自体もスタンドアロンで使用できます。たとえば、'progressbar 10 "Creating directory tree";' を送信します。表示されます:
[####### ] (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"
前回の実行からの目標行数に対するコマンド出力の行数に基づいて進行状況を追跡したかったのです。
#!/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
次のような進行状況バーを出力します。
[===============================================> ]
出力行数が目標に達すると、バーが大きくなります。行数が目標を超えた場合、バーは最初からやり直します (うまくいけば、目標は良好です)。
ところで: Mac OSX で bash を使用しています。このコードはmariascioのスピナーに基づいています。
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