views:

235

answers:

3

Hello,

In addition to my previous query concerning multi-threading in shell scripting I am curious if its possible to have multiple progress bar.

Here is a code snippet of my expected result :

Output : 1 of 100  Files Completed   # Thread1
Output : 10 of 45 files Completed     # Thread 2    

The lines are updated showing the progress. Would it be possible to implement it shell. Would appreciate your help if anyone has a idea on this.

Regards Kiran

+1  A: 

Like what pv -c does?

Sure, see src/pv/cursor.c. Although it's not really safely doable from shell only, a small C utility could take care of it.

ephemient
is it possible with printf statements in shell with different escape characters \n \r etc ??
Kiran
Not really. How do you prevent the two threads from writing to the terminal at the same time and messing the lines up? And you have to use ANSI escapes to set the cursor position *up* a line.
ephemient
Ya ephemient, That's what I am looking at. If this is possible using shell script alone.. I know, I am expecting too much.. Would be nice if I could do it..
Kiran
+1  A: 

Yes, it is very much possible.

Assuming that your existing code (based on your prior posts) currently goes something like this:

do_something() {
  ...
  echo -ne "\r$index of $No_of_Files Completed"
  ...
}

do_something A &
do_something B &
do_something C &
wait

...then you can perform the following adjustments in order to achieve the effect that you had in mind:

# Background tasks will no longer write directly to the console; instead, 
#  they will write to temporary files which will be read periodically 
#  by a special log printer task (which will display everything nicely.) 
# 
# Name of temporary files 
STATUS_BASENAME="/tmp/~$$.status" 
# Process IDs of backgrounded tasks; we record them so we can wait on them 
#  specifically but not wait on the special log printer task 
TASK_PIDS="" 

do_something() { 
  # First parameter must be a task ID starting at 0 incremented by 1 
  TASK_ID=$1 ; shift 
  ...
  # We write new status to status file (note we don't echo -n, we want that 
  #  trailing newline) 
  # Try to go through a temporary status file which we rename afterwards to 
  #  avoid race conditions with the special log printer task 
  echo "$x of 5 Completed" >"${STATUS_BASENAME}.${TASK_ID}.tmp" 
  mv "${STATUS_BASENAME}.${TASK_ID}.tmp" "${STATUS_BASENAME}.${TASK_ID}" 
  ...
} 

# Special log printer task 
status_printer() { 
  # First time in the loop is special insofar we don't have to 
  #  scroll up to overwrite previous output. 
  FIRST_TIME=1 
  while true ; do 
    # If not first time, scroll up as many lines as we have 
    #  regular background tasks to overwrite previous output. 
    test $FIRST_TIME -eq 0 && for PID in $TASK_PIDS ; do 
      echo -ne '\033M' # scrol up one line using ANSI/VT100 cursor control sequences 
    done 
    FIRST_TIME=0
    TASK_ID=0
    for PID in $TASK_PIDS ; do 
      # If status file exists print first line 
      test -f "${STATUS_BASENAME}.${TASK_ID}" && head -1 "${STATUS_BASENAME}.${TASK_ID}" || echo "waiting..." 
      TASK_ID=`expr $TASK_ID + 1` # using expr for portability :) 
    done 
    test -f "${STATUS_BASENAME}.done" && return
    sleep 1 # seconds to wait between updates 
  done 
} 

do_something 0 A &
 TASK_PIDS="$TASK_PIDS $!" 
do_something 1 B & 
 TASK_PIDS="$TASK_PIDS $!" 
do_something 2 C &
 TASK_PIDS="$TASK_PIDS $!" 

status_printer & 
  PRINTER_PID=$! 

# Wait for background tasks 
wait $TASK_PIDS 

# Stop special printer task instead of doing just
#  kill $PRINTER_PID >/dev/null
touch "${STATUS_BASENAME}.done"
wait $PRINTER_PID

# Cleanup 
rm -f "${STATUS_BASENAME}."* 
vladr
Thanks a lot for taking your time out in developing this code..I will try this if it fits in my framework. I was thinking if there was some obvious way of using a combination of echo and printf statements. Appreciate all your help
Kiran
A: 

If your subprocess output is filterable then you can redirect stdout of each subproc to a different file descriptor and then call a function to loop through that file descriptor's output. Then you can try to use printf to update the progress meter. This is where you will run into trouble though. Not sure Bash's builtin printf can handle the terminal to that detail. I never tried to write a progress meter in Bash that does multi-line output. Why not use dialog?

Justin