0

I am trying to create a bash Dialog (whiptail) progress bar for a list of commands. So, that progress bar shows along with messages when the commands get executed one by one in the background. I am using whiptail, this is a Ubuntu server, so, I am not looking at GTK based utilities that require an Xserver e.g. Zenity, Yad etc. I am very new to bash scripting and I don't know where I am getting wrong any help is appreciated.

System OS: Ubuntu 20.04

The Issue:

The messages are getting displayed all at once instead of stages/per command and all the commands start executing parallelly (all at once) instead of one by one. Is the code wrong? should I have used for loop instead of while loop, if then how?

my list of commands:

{
        sudo apt-get update -y
        sudo apt-get install nginx -y
        sudo systemctl reload nginx
        sudo add-apt-repository ppa:ondrej/php -y
        sudo apt-get -y install php-fpm
        sudo apt-get install mariadb-server -y
}

This is the code for the progress bar:

#!/usr/bin/env bash
(
msgs=( "Preparing install..." "Starting Nginx installation..." "Nginx installation completed successfully" "Starting Mariadb installation..." "Starting PHP installation..." "PHP installation completed successfully" )
items=$(
        {
            #echo "Preparing install..."
            sudo apt-get update -y
            #echo "Starting Nginx installation..."
            sudo apt-get install nginx -y
            #echo "Nginx installation completed successfully"
            sudo systemctl reload nginx
            #echo "Starting Mariadb installation..."
            sudo apt-get install mariadb-server -y
            #echo "Starting PHP installation..."
            sudo add-apt-repository ppa:ondrej/php -y
            sudo apt-get -y install php-fpm
            #echo "PHP installation completed successfully"
            sudo systemctl reload nginx
        } | wc -l)
processed=0
while [ $processed -le $items ]; do
    pct=$(( $processed * 100 / $items ))
    echo "XXX"
    echo $processed
    echo ${msgs["$processed"]}
    echo XXX
    echo "$pct"
    processed=$((processed+1))
    sleep 1
done
) | whiptail --title "Gauge" --gauge "Please wait..." 10 60 0

These are the messages:

msgs=( "Preparing install..." "Starting Nginx installation..." "Nginx installation completed successfully" "Starting Mariadb installation..." "Starting PHP installation..." "PHP installation completed successfully" )

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
depar
  • 89
  • mmhh, what do you think the assignment and command substitution items=$( { sudo apt-get update -y ...} | wc -l ) does..? – ilkkachu Sep 16 '21 at 20:46
  • @ilkkachu it counts the number of items/commands in the command list, maybe I am wrong – depar Sep 16 '21 at 21:11
  • it would run the commands, and pipe their output to wc, which counts the lines in output. The { ... } construct is just for command grouping, it doesn't make that an array or anything like that. Even if you did have the commands in an array, there doesn't seem to anything in the main loop to actually run them. What's the goal here? To run one command per loop iteration, I suppose? – ilkkachu Sep 16 '21 at 21:15
  • yes, run one command per loop iteration and show message along with that on the progress bar – depar Sep 16 '21 at 21:17

1 Answers1

0

I think you're looking to do something in this direction:

msgs=("Preparing install..."
      "Starting Nginx installation..."
      ...
     )
commands=("sudo apt-get update -y"
          "sudo apt-get install nginx -y"
          ...
         )

n=${#commands[@]} i=0 while [ "$i" -lt "$n" ]; do pct=$(( i * 100 / n )) echo XXX echo $i echo "${msgs[i]}" echo XXX echo "$pct" eval "${commands[i]}" i=$((i + 1)) done | whiptail --title "Gauge" --gauge "Please wait..." 10 60 0

The main point here being two arrays, one for the messages, one for the commands. Usually, you'd want to use a full array for a single command line (see How can we run a command stored in a variable?), but we don't have 2D arrays, so storing them as strings to eval can work as a substitute.

(I didn't fix that to get the percentages right, it's late.)

depar
  • 89
ilkkachu
  • 138,973
  • This works!! But had to make few changes - echo XXX echo $i echo "${msgs[i]}" echo XXX echo "$pct" eval "${commands[i]}" i=$((i + 1)) sleep 1 – depar Sep 16 '21 at 21:48
  • I do have a question, as I see command list is now an array, will I be able to use any command that uses alias for cd – depar Sep 16 '21 at 21:51
  • @depar, aliases in general don't work in non-interactive shells, i.e. in scripts. But also no, they won't work if the command name comes from an expansion, aliases are processed really early in the command line parsing. Use functions instead, there's some questions in here about them, https://unix.stackexchange.com/questions/30925/in-bash-when-to-alias-when-to-script-and-when-to-write-a-function seems to be highly-voted one – ilkkachu Sep 17 '21 at 08:09