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

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

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


当前回答

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

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

其他回答

大多数unix命令不会为您提供可以执行此操作的直接反馈。 有些将在您可以使用的stdout或stderr上提供输出。

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

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

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

预览

我把它放在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

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

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

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

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

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

代码:

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

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

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

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

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

命令ps a将列出所有带有id的进程,您需要grep来查找指定进程的pid

这是一个由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"