496

*NIX の bash またはその他のシェルでスクリプトを作成する場合、コマンドの実行中に数秒以上かかる場合は、進行状況バーが必要です。

たとえば、大きなファイルをコピーしたり、大きな tar ファイルを開いたりします。

プログレス バーをシェル スクリプトに追加するには、どのような方法をお勧めしますか?

4

42 に答える 42

766

これは、行を上書きすることで実装できます。端末\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 は、長い行から始めて短い行を書きたい場合、これが「失敗する」と述べています。この場合、長い行の長さを (たとえば、スペースで) 上書きする必要があります。

于 2008-10-26T14:47:53.280 に答える
106

スピナーのやり方にも興味があるかもしれません:

Bashでスピナーを実行できますか?

もちろん!

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
于 2010-07-25T20:12:20.817 に答える
55

Linux コマンドを使用しpvます。

パイプラインの途中にある場合、サイズはわかりませんが、速度と合計が得られます。そこから、所要時間を計算してフィードバックを取得できるため、ハングしていないことがわかります。

于 2011-07-28T17:35:46.257 に答える
49

いくつかの投稿では、コマンドの進行状況を表示する方法が示されています。それを計算するには、どれだけ進んだかを確認する必要があります。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") に掲載されています。

于 2008-10-26T15:18:26.010 に答える
45

選択した回答よりもセクシーなものを探していたので、自分のスクリプトもそうしました。

プレビュー

進行中のprogress-bar.sh

ソース

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
于 2016-10-06T14:17:54.930 に答える
28

似たようなものは見たことがなく、ここのすべてのカスタム関数はレンダリングのみに焦点を当てているようです...この質問は簡単ではないため、以下の非常に単純な POSIX 準拠のソリューションと段階的な説明を以下に示します。

TL;DR

プログレス バーのレンダリングは非常に簡単です。どのくらいレンダリングする必要があるかを見積もることは別の問題です。プログレス バーをレンダリング (アニメーション化) する方法は次のとおりです。この例をコピーしてファイルに貼り付けて実行できます。

#!/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反復ごとに変数を計算します。上記の計算を行うには、次の要素が必要です。

  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- 下から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 点です。

  1. ${}: 上記の例では、 を使用しています${foo:A:B}。この構文の専門用語はParameter Expansionです。これは、変数 (パラメーター) を操作できる組み込みのシェル機能です。たとえば、文字列をトリミングする:だけでなく、他のことも実行できます。サブシェルは生成されません。私が考えることができるパラメーター展開の最も顕著な説明 (完全には POSIX 互換ではありませんが、読者が概念をよく理解できるようにするもの) は、man bashページにあります。
  2. $(): 上記の例では、 を使用していますfoo=$(bar)サブプロセス (別名Subshel​​l ) で別のシェルを生成し、barその中でコマンドを実行し、その標準出力を$foo変数に割り当てます。これはプロセス置換と同じではなく、パイプ( )とはまったく異なるもの|です。最も重要なことは、それが機能することです。これは遅いので避けるべきだと言う人もいます。このコードが視覚化しようとしているものは何でも、進行状況バーが必要なほど長く続くため、これはここでは「問題ありません」と主張します。つまり、サブシェルはボトルネックではありません。サブシェルを呼び出すと、終了ステータスreturnとは何か、ほとんどの人が考えているものではない理由を説明する手間も省けますまた、シェルの関数から値を渡すことが、シェル関数が一般的に得意とすることではない理由。そのすべてについて詳しく知るには、このページを強くお勧めしman bashます。
于 2016-12-01T01:21:15.500 に答える
18

GNU tarには、単純なプログレス バーの機能を提供する便利なオプションがあります。

(...) もう 1 つの使用可能なチェックポイント アクションは、「ドット」(または「.」) です。これは tar に、標準のリスト ストリームに 1 つのドットを出力するように指示します。たとえば、次のようになります。

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

以下の方法でも同じ効果が得られます。

$ tar -c --checkpoint=.1000 /var
于 2010-08-02T14:15:23.667 に答える
14

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
于 2010-07-25T20:07:26.520 に答える
5

ほとんどの UNIX コマンドは、これを実行できるような直接的なフィードバックを提供しません。使用できる stdout または stderr に出力を提供するものもあります。

tar のようなものでは、-v スイッチを使用して出力をプログラムにパイプし、読み取った行ごとに小さなアニメーションを更新することができます。tar がファイルのリストを書き出すと、プログラムはアニメーションを更新できます。完了率を計算するには、ファイルの数を知り、行数を数えなければなりません。

私が知る限り、cp はこの種の出力を提供しません。cp の進行状況を監視するには、ソース ファイルと宛先ファイルを監視し、宛先のサイズを監視する必要があります。ファイル サイズを取得するstat (2)システム コールを使用して小さな C プログラムを作成できます。これにより、ソースのサイズが読み取られ、宛先ファイルがポーリングされ、現在までに書き込まれたファイルのサイズに基づいて % 完了バーが更新されます。

于 2008-10-26T14:42:52.167 に答える
4

私のソリューションは、現在圧縮解除されて書き込まれている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}
}
于 2010-08-24T09:01:34.087 に答える
3

--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"

于 2016-01-10T17:40:47.220 に答える
1

これは、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
于 2014-03-17T12:25:41.193 に答える
0

一時的な進行状況バーを表示する必要がある場合 (表示時間を事前に知っておくことで)、次のように 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

于 2015-03-27T09:45:51.667 に答える
0

私は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'
于 2016-02-10T11:54:02.017 に答える
-1

これは、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"
于 2016-05-17T19:56:59.357 に答える
-1

前回の実行からの目標行数に対するコマンド出力の行数に基づいて進行状況を追跡したかったのです。

#!/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のスピナーに基づいています。

于 2016-07-08T23:59:02.297 に答える
-3

tar プログレス バーを作成するには

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

ここで、「19」は、tar 内のファイル数を目的のプログレス バーの長さで割ったものです。例: .tgz には 140 個のファイルが含まれており、プログレス バーを 76 "." にしたい場合は、-L 2 を指定できます。

他に何も必要ありません。

于 2012-05-30T10:40:40.520 に答える
-5

今日も同じことをしなければなりませんでした。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
于 2013-05-02T11:40:11.703 に答える