Version 5 of text mode (terminal) progress bar / progressbar

Updated 2007-02-09 21:36:10

Wrote this one because I didn't find this one: Progress. In some ways mine is simpler and prettier (although code is less readable). In case someone finds it useful - here it is, up for the taking (or improving)...

 proc progres {cur tot} {
   # if you don't want to redraw all the time, uncomment and change ferquency
   #if {$cur % ($tot/300)} { return }
   # set to total width of progress bar
   set total 76

   set half [expr {$total/2}]
   set percent [expr {100.*$cur/$tot}]
   set val (\ [format "%6.2f%%" $percent]\ )
   set str "\r|[string repeat = [expr {round($percent*$total/100)}]][string repeat { } [expr {$total-round($percent*$total/100)}]]|"
   set str "[string range $str 0 $half]$val[string range $str [expr {$half+[string length $val]-1}] end]"
   puts -nonewline stderr $str
 }

2007-02-09 VI Another one modeled after the one from wget. Auto updates every 5% or 5s.

 32%|================..................................|    1600|ETA:      4s


 proc progress_init {tot} {
    set ::progress_start     [clock seconds]
    set ::progress_last      0
    set ::progress_last_time 0
    set ::progress_tot       $tot
 }

 # We update if there's a 5% difference or a 5 second difference

 proc progress_tick {cur} {
    set now [clock seconds]
    set tot $::progress_tot

    if {$cur > $tot} {
        set cur $tot
    }
    if {($cur >= $tot && $::progress_last < $cur) ||
        ($cur - $::progress_last) >= (0.05 * $tot) ||
        ($now - $::progress_last_time) >= 5} {
        set ::progress_last $cur
        set ::progress_last_time $now
        set percentage [expr round($cur*100/$tot)]
        set ticks [expr $percentage/2]
        if {$cur == 0} {
            set eta   [format %10s Unknown]
        } else {
            set eta   [format %7d [expr int(($tot - $cur) * ($now - $::progress_start)/$cur)]]s
        }
        set lticks [expr 50 - $ticks]
        set str "[format %3d $percentage]%|[string repeat = $ticks]"
        append str "[string repeat . $lticks]|[format %8d $cur]|ETA:$eta\r"
        puts -nonewline stdout $str
        if {$cur >= $tot} {
            puts ""
        }
        flush stdout
    }
 }

 # Sample Test: 

 progress_init 5000

 for {set i 0} {$i < 6200} {incr i 200} {
    progress_tick $i
    after 200
 }

CAU I wrote this some time ago as an exercise. It displays a fancy sliding bar for the user while a long running command is in progress.

Use on bourne/korn shell.

 # -- waitbar
 #
 #   Shows a flashy progress bar to the user on their terminal while a
 #   background process executes.
 #
 #   Uses a Tcl interpreter to manage the screen updating!
 #
 # Arguments:
 #   See usage message below. Function accepts EITHER a command line or a PID.
 #
 # Result:
 #   Outputs a wait bar on the terminal.
 waitbar() {

  unset pid cmdline
  OPTIND=0
  barLength=15
  barTime=2000
  barChars="-#"
  sliderLength=3
  reverse=0

  usage="Usage: waitbar [ -l<bar-length> ] [ -s<slider-length> ] [ -t<bar-time> ] [ -d<display-chars> ] [ -r ] <pid> | <command-line>"

  if [ $# -eq 0 ] ; then
      echo "No options supplied!" 1>&2
      echo "$usage" 1>&2
      return 1
  else
    while getopts "l:t:d:s:r" opt "$@"
    do
      case $opt in
        d)
          barCharStr=$OPTARG
          if [ `printf $barCharStr | wc -m` -eq 2 ] ; then
              barChars=$barCharStr
          fi
        ;;
        l)
          barLength=$OPTARG
        ;;
        s)
          sliderLength=$OPTARG
        ;;
        t)
          barTime=$OPTARG
        ;;
        r)
          reverse=1
        ;;
        \?)
          echo "$usage" 1>&2
          return 1
        ;;
      esac
    done
  fi

  # Resolve whether were using a command line or a PID
  shift `expr $OPTIND - 1`

  if [ $# -ne 1 ] ; then
      echo "$usage" 1>&2
      return 1
  fi

  userOpt=`echo "$1" | nawk '{
      if (match($0,"^[0-9]+$") > 0) { 
          printf "P"
      } else {
          printf "C"
      }
    }'`

  if [ "$userOpt" = "C" ] ; then
      cmdline="$1"
  else
      pid="$1"
  fi

  ttyStr=`tty`

  # Lock the terminal from any distorting iuser input.
  stty -echo
  stty -echonl
  {

    if [ "$userOpt" = "C" ] ; then
        eval "$cmdline &"
        pid=$!
    fi

    # Only show the waitbar if script is being run from a terminal
    if [ "$ttyStr" != "" ] ; then
        echo "set pid $pid
              set barLength $barLength
              set barChars \"$barChars\"
              set barduration $barTime
              set lagLength $sliderLength
              set reverse $reverse
              "'
        fconfigure stdout -buffering none
        #set barChars "-#"
        #set duration 10000
        set duration 0
        #set barduration 2000
        #set barLength 15
        #set lagLength 3

        set barduration [expr $barduration * 1.0]
        set interval [expr {round($barduration/$barLength)}]
        set fillchar [string index $barChars 0]
        set poschar [string index $barChars 1]
        set finished 0
        set timesofar 0
        set bartime 0
        set position 1
        set forward 1
        while {! $finished} {
            set forebar ""
            set aftbar ""
            set posString ""
            while {1} {
                set posString "$poschar$posString"
                set posStrLen [string length $posString]
                if {(($posStrLen >= $lagLength) || ($posStrLen >= $position)) || \
                    ( [expr {$barLength - $posStrLen}] <= [expr {$position-$lagLength}] ) } {
                    break
                }
            }
            set posStrLen [string length $posString]
            if {$position <= $barLength} {
                set aftbar  [string repeat $fillchar [expr $barLength - $position]]
            }
            set aftbarLen [string length $aftbar]
            set forebarLen [expr {$barLength-($posStrLen+$aftbarLen)}]
            set forebar [string repeat $fillchar $forebarLen]
            set aftbar  [string repeat $fillchar [expr {$barLength - ($forebarLen + $posStrLen)}]]

            # There is a difference in how various unixes pass through data
            # to tclsh, so in some cases, the <CR> will need to be protected
            # with another slash.  This is true on SunOS 5.8.
            set outStr [format "\\r\[$forebar$posString$aftbar\]" ]
            if {$reverse && (! $forward)} {
                set outStr [format "\\r\[$aftbar$posString$forebar\]" ]
            }
            puts -nonewline $outStr
            flush stdout
            after $interval
            incr bartime $interval
            incr timesofar $interval
            incr position
            if {$position >= [expr {$barLength + $lagLength}]} {
                set bartime 0
                set position 1
                set forward [expr {abs($forward -1)}]
            }
            if {$timesofar >= $duration} {
                if {$duration == 0} {
                    set timesofar 0
                    if {[exec ps -opid= -p $pid] == ""} {
                        set finished 1
                    }
                } else { 
                    set finished 1
                }
            }
        }
        ' | tclsh &
        tclpid=$!
        wait $pid
        wait $tclpid
        printf "\\r                                                                                \\r"
    else
        wait $pid
    fi
  } 2> /dev/null
  stty echo
  stty echonl
  return 0
 }

clear screen


Category Text Screen