text mode (terminal) progress bar / progressbar

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)...

See Also

clear screen

Description

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
}

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

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

Updated to print Total time taken at end - rather than reducing count.

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   ETA:[format %7s Unknown]
       } elseif {$cur >= $tot} {
           set eta   TOT:[format %7d [expr int($now - $::progress_start)]]s
       } else {
           set eta   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\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
}