Linux Apprentice: Improve Bash Shell Scripts Using Dialog

 in
The dialog command enables the use of window boxes in shell scripts to make their use more interactive.

Shell scripts are text files containing commands for the shell and are frequently used to handle repetitive tasks. In order to avoid typing the same commands over and over again, we put them in a file with a few modifications, give it execute permission and run it.

To control the program at run-time, an interactive shell script is needed. For this case, the dialog command offers an easy way to draw text-mode colored windows. These windows can contain text boxes, message boxes or different kinds of menus. There are even ways of using input from the user to modify the script behaviour.

The current version of the dialog program is cdialog-0.9 and can be freely downloaded from Sunsite's /pub/Linux/utils/shell directory. Dialog uses the ncurses library, so it too must be installed. Some Linux distributions (i.e., Slackware) include the dialog program because of utilities which rely on it (setup, pkgtool). By the way, these utilities are great examples of using dialog.

Let's examine the dialog version of the most popular example program around. With your favorite text editor, create a file named hello containing these lines:

#!/bin/sh
# First shell script with "dialog"
dialog --title "Dialog message box" \
       --msgbox "\n Hello world !" 6 25

The first line of this file identifies it as a shell script for the “sh” shell. Every shell script must start with the characters “#!” followed by the name (and path) of the shell to execute. For example, we could have written this line as #!/bin/bash. The next line is just a comment, like any line starting with “#” other than the first line in the file. Then comes the dialog command, which will draw a message box 6 lines high and 25 columns wide on the screen, containing the title “Dialog message box” and the message “Hello world !”. The message box has an OK button and when it is selected, the script will end. Notice the general format of the --msgbox option:

--msgbox
After writing and saving this file, type:
chmod a+x hello
=“./2460f1.gif” Figure 1. Screenshot of a Dialog Box

The resulting screen is shown in Figure 1. This example is so simple it could have been produced with just one command at the shell prompt. However, things get more complicated when user input is needed in a shell script.

For example, to list the contents of a directory, use dialog as shown in Listing 1. This introduces two new dialog boxes: an input and a text box. The input box has the general format:

--inputbox

In Listing 1, the default value displayed in the input box is obtained by running the command pwd which returns the present working directory. Whenever a command is enclosed in reverse quotes, bash replaces it with its standard output.

Of course, this default value can be changed at runtime using the backspace key to delete and regular letter keys to write. The final value is printed by dialog on STDERR. In order to use it from the shell script, it must first be redirected to a file. Do this with the redirection:

2>/tmp/dialog.ans

The next line is necessary in case the user decides to select the Cancel button in the input box. When that happens, the exit status of the dialog command will be 1. Bash keeps the exit status of the last executed command in the variable $?, so if this is 1, the shell script will stop after clearing the screen.

If $? is 0 (the user clicked the OK button), the answer file is read to set the variable ANS. Again, reverse quoting proves useful. Another method of doing this is to use:

ANS=$(cat /tmp/dialog.ans)

The contents of the chosen directory are output to the same file used before. This can be done safely, because the > operator overwrites the previous contents of this file.

All is now set for the next dialog command, which generates the text box to display the contents of a text file. It has the general format:

--textbox

The text box allows navigating with the arrow keys or home/end/pgup/pgdown keys and even has simple searching facilities. Typing / while the text box is displayed causes another window to appear, which prompts the user for a string to be searched forward in the file. Typing ? performs reverse searching, just as for the less pager. The first line containing the string is displayed at the top of the text box.

The experienced programmer might complain about an obvious flaw in this shell script. What if the directory name is wrong? The shell script will not complain, but will show an empty text box since there are no files in a nonexistent directory. To solve this problem, a check is made to see if the specified directory exists. Actually, the ls command returns an exit status of 0 if the directory exists, and 1 if it doesn't. Thus, the script can be modified by adding these lines:

ls -al $ANS > /tmp/dialog.ans 2>/dev/null
if [ $? = 1 ]; then
   clear
   echo no such directory
   exit 1
fi

First, the ls line is changed, redirecting standard error to /dev/null. This means no error messages from ls will appear on the screen. Then, if the exit status ($?) is 1, the script will exit with an error message.

This script can be made even more useful by allowing the user to examine more directories before the script exits. (See Listing 2.) A few changes have been made. First, the entire script has been included in a while-do loop which is always true. This allows it to run more than once. Now the only way of exiting the script (besides typing ctrl-c) is by selecting the Cancel button in the dialog input box. The second change is the introduction of a message box which will be displayed when the ls command returns an exit status of 1. The command continue deserves a special comment. Its meaning is to skip the current iteration of the while loop (i.e., the part which shows the text box) and start a new one. Thus, after the error message, the user will again see the input box, prompting for another directory name.

The menu box is produced by running dialog with the --menu option with the format:

dialog --menu
   tag2 item2...

This option displays a box with two buttons (OK and Cancel) and a menu consisting of one or more lines. Each line has a “tag” (a number or word) and an “item”, which is usually text describing the menu entry. When a user selects an item and then clicks on the OK button, the corresponding tag is printed on STDERR. Also, the exit status of dialog is 0 for the OK button being selected and 1 for the Cancel button.

Menu boxes are useful in that they allow the user to choose from several fixed alternatives. For example, when producing a LaTeX document, three steps must be taken: editing the source file with a word processor, compiling it with LaTeX and viewing the resulting .dvi file. It is easy to build a shell script to do these steps. (See Listing 3 which assumes the text editor is jed, the .dvi file viewer is dvisvga and both are in the path.) The complete script is again included in a “while” loop for the purpose of making it work more than one time. The only way to exit this script is by selecting the “Cancel” button in the first menu box. Otherwise, the user has to choose between three alternatives:

  • Edit a text file.

  • Compile a LaTeX file.

  • View a .dvi file.

The answer is stored in the file /tmp/ans and retrieved in the variable R. If the user chooses to edit a file, a new dialog box appears. It is an input box and prompts for a file name. The answer goes into the variable F. Then the script checks whether the file exists and runs the command:

jed $F # where $F is the name of the file
If the file does not exist, it is either a new one or a typing error. To distinguish between these two possibilities, a yes/no dialog box is provided. The general format of such a box is:
--yesno
The box has two buttons, YES and NO. The text is usually a question, which the user answers by selecting a button. If YES, $? (the exit status of the dialog command) is 0; if NO, $? is 1.

In Listing 3, if the answer is YES, the text editor is invoked; if NO, the script returns to the main menu through the continue command. The other two choices work in the same way, the only difference being the commands for processing the file with LaTeX or for viewing the resulting DVI file:

latex $F
dvisvga $F

Several other dialog boxes are available, such as the checklist or the radiolist; however, their use is quite similar to that of the menu box.

I would like to end with an example of the --guage dialog box. This is used to graphically display a percentage. The syntax is:

dialog --guage

Once started, the guage box keeps reading percent values from STDIN until an EOF is reached and changes the display accordingly. Here is a simple (but not very useful) guage script:

#!/bin/bash
{ for I in 10 20 30 40 50 60 70 80 90 \
      80 70 60 50 40 30 20 10 0; do
   echo $I
   sleep 1
done
echo; } | dialog --guage "A guage demo" 6 70 0
Copy this into a file, give it execute permission, run and enjoy! The first part of the script (included in braces) is a group command. Every second it sends one of the listed values to the guage dialog box. The final echo command is used to terminate the dialog box.

Shell scripting is a convenient way of making your Linux system “smarter”. These examples of the most common dialog boxes should help you make your scripts more attractive.

Resources

Mihai Bisca is an ophthalmologist who is crazy about Linux. In 1998 he published the first Romanian introductory book on Linux. Currently, he is competing with his three-year-old daughter Andra for the place at the keyboard. You can reach them at mbasca@ottonel.pub.ro.

______________________

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Echo current actions to dialog

Mike Stewart's picture

Hi, thanks, your scripts have helped me to get started. I have now written one to display a menu which allows me to select one of my servers to backup (using rsync). I also looked at you 'gauge' script and would like to find a way to use this to echo current progress - the filename which rsync is transfering. Below is my backup script, so far...

-------------------------------------------------------------------------
#!/bin/sh

fileroot=/bin
array=(Ystradgynlais Brecon Llandrindod Newtown Welshpool Machynlleth Corporate)
n=1
for item in ${array[@]}
do
menuitems="$menuitems $n ${item}"
let n+=1
done

dialog --menu \
"Select server to backup" \
14 40 7 $menuitems 2> /tmp/$$
if [ $? -gt 0 ]; then
rm -f /tmp/$$
clear
echo "Operation Cancelled"
exit 0
fi
selection=${array[$(cat /tmp/$$) -1]}
echo "You selected $selection"

basedest="/home/backups/manual"

if [ "$selection" = "Machynlleth" ] ; then
destination=$basedest/"canolfan_$(date +%Y%m%d)"
server="172.16.20.20"

elif [ "$selection" = "Brecon" ] ; then
destination=$basedest/"brecon_$(date +%Y%m%d)"
server="172.16.86.7"

elif [ "$selection" = "Llandrindod" ] ; then
destination=$basedest/"llandrindod_$(date +%Y%m%d)"
server="172.19.166.7"

elif [ "$selection" = "Welshpool" ] ; then
destination=$basedest/"welshpool_$(date +%Y%m%d)"
server="172.16.155.6"

elif [ "$selection" = "Newtown" ] ; then
destination=$basedest/"newtown_$(date +%Y%m%d)"
server="172.19.170.7"

elif [ "$selection" = "Ystradgynlais" ] ; then
destination=$basedest/"ystradgynlais_$(date +%Y%m%d)"
server="172.19.171.7"

elif [ "$selection" = "Corporate" ] ; then
destination=$basedest/"corporate_$(date +%Y%m%d)"
server="172.16.15.9"

else
exit 0
fi

rsync -e ssh -avz --exclude-from=/usr/sbin/excludes.txt root@$server:/home $destination/
rsync -e ssh -avz root@$server:/etc/samba $destination/samba/
rsync -e ssh -avz root@$server:/etc/passwd $destination/passwd/
rsync -e ssh -avz root@$server:/etc/group $destination/group/

exit 0
-------------------------------------------------------------------------

Any help would be much appreciated.

Mike.

bisca command of unix

nahidh's picture

help me by line command of unix

Gauge and others

Anonymous's picture

Funnily your bash script using --guage works even if misspelled, i.e. the correct parameter is --gauge.

Another version of the script would look as follows:

#!/bin/bash

{ for I in $(seq 1 100) ; do
echo $I
sleep 0.01
done
echo 100; } | dialog --shadow --gauge "Progress" 6 70 0

In this case the bar stays a 100 in the end.
In any case thanks a lot for this article. This is exactly what I was looking for.

I got inspired to make a file selection menu:

#!/bin/sh

# File or Directory selection menu with dialog
fileroot=/bin
array=( $(ls $fileroot) )
n=0
for item in ${array[@]}
do
menuitems="$menuitems $n ${item}"
let n+=1
done

dialog --title "Select a file" --menu \
"Choose one of the following or press to exit" \
14 40 6 $menuitems 2> /tmp/$$
if [ $? -gt 0 ]; then
rm -f /tmp/$$
clear
echo "Interrupted"
exit 0
fi
selection=${array[$(cat /tmp/$$)]}
echo "You selected $selection"

George

Menu with array

gabe's picture

The menu with array is really intresting, but what if instead of use an array of file-names, you use an array of elements that have a space in the middle of the element?

Two ways to implement dynamic menus without arrays

Anonymous's picture

Also see here: http://pastebin.com/f5a053e2a

#! /bin/sh

# If MAX_ARG_PAGES is 32 and page size is 4KB, then there is only 128kB for environment variables and arguments.
# Since dynamic menu items are passed in via command line, only return about 100kB of data or less.
# Any more, and there will be an error when launching the menu.
FILEMENU_MAXNUMBEROFFILES=60
FILEMENU_MAXAGEINDAYS=180

function listFiles() {
DirPath=$1
ModifyTime=$2

find "$DirPath" -maxdepth 1 -mindepth 1 -type f -mtime "$ModifyTime" -print
}

function listFilesForMenuXarg() {
DirPath=$1
ModifyTime=$2

listFiles "$DirPath" "$ModifyTime" | tail -n $FILEMENU_MAXNUMBEROFFILES | while read filePath; do
echo -e -n "$(basename "$filePath")\0000$(date -r /opt "+%Y-%m-%d") $(cat "$filePath" | wc -c)\0000"
done
}

function fileMenuXarg() {
DirPath=$1

while [ true ]; do
exec 3>&1
exec 4>&1
fileName=$(listFilesForMenuXarg "$DirPath" "-$FILEMENU_MAXAGEINDAYS" 0>&4 | xargs -0 dialog --input-fd 4 --default-item "$fileName" --menu "Xarg Test - Select a file." 0 0 0 2>&1 1>&3)
returnCode=$?
exec 3>&-
exec 4>&-

if [ $returnCode -ne 0 ]; then
return
fi

cat "$DirPath/$fileName" | less
done
}

function listFilesForMenuSet() {
DirPath=$1
ModifyTime=$2

listFiles "$DirPath" "$ModifyTime" | tail -n $FILEMENU_MAXNUMBEROFFILES | while read filePath; do
echo "$(basename "$filePath")"
echo "$(date -r /opt "+%Y-%m-%d") $(cat "$filePath" | wc -c)"
done
}

function fileMenuSet() {
DirPath=$1

while [ true ]; do
IFS=$'\n'
set $(listFilesForMenuSet "$DirPath" "-$FILEMENU_MAXAGEINDAYS")
IFS=$' \t\n'

exec 3>&1
fileName=$(dialog --default-item "$fileName" --menu "Set Test - Select a file." 0 0 0 "$@" 2>&1 1>&3)
exec 3>&-
returnCode=$?
if [ $returnCode -ne 0 ]; then
return
fi

cat "$DirPath/$fileName" | less

done
}

# You must have some files in your home directory for this example to work.
fileMenuSet "$HOME"
fileMenuXarg "$HOME"

array of elements that have a space in the middle of the element

Fred Teeuwen's picture

It would be realy nice to see the answer to this question. When quoting and escape characters don't work, it's only guessing to what the dialog programmers choose to let this work. If an answer exists, please let me know. Thanks in advance.

array of elements that have a space in the element

Fred Teeuwen's picture

Found 1 way to do it, although it is not a very elegant way, so I am curious if somebody can come up with a better solution.
The solution I use now is to build up the complete dialog command in a file and then execute the file within the shell. For example build the above $menuitems like:
Tag=“Cron”
Item=“Perform cron administration”
echo “$Tag” > /tmp/menuitems.$$
echo “$Item” >> /tmp/menuitems.$$
Tag=“Global Reports”
Item=“Show Global Reports”
echo “$Tag” >> /tmp/menuitems.$$
echo “$Item” >> /tmp/menuitems.$$
Tag=”Log files”
Desc=”Perform Logfile Management”
echo “$Tag” >> /tmp/menuitems.$$
echo “$Item” >> /tmp/menuitems.$$
#(better to build this up in loop of course)
Title=”Task Manager”
export Title
MenuHeight=6
export MenuHeight

# now build the dialog command

BldMenu="dialog --clear --title \"$Title\" \
--menu \" Use arrow keys, or first character \n\
Select with \n\
Your choice:\" 30 82 $MenuHeight \
$(cat /tmp/menuitems.$$|while read item;do echo "\"${item}\"";done) \
2>/tmp/dialans.$$"

# put the dialog command in a file
echo ${BldMenu} >/tmp/dialbuild.$$

# execute the dialbuild file (note the dot-space in the front)
. /tmp/dialbuild.$$

#At least it works (:-|)

Dynamic Menus with Spaces in the elements

Zach Loeber's picture

Here is how I figured out how to do this. It has to do with how the strings get delimited in bash. For this example I build a list of menu items from a pre-existing array of interface names. I build the array by referencing the count of its elements as the index to write to (thus each assignment adds another element to the end). So if var_ifarray has only one element then menulist will have that element as the tag and some text as the description.

declare menulist
declare i

menulist=( )
for i in ${var_ifarray[@]}; do
if [ $? -ne 0 ]; then
menulist[${#menulist[@]}]="$i"
menulist[${#menulist[@]}]="interface $i"
fi
done
#if you want to just quickly test this you can just define
# a bunch of elements like so:
# menulist[${#menulist[@]}]="1"
# menulist[${#menulist[@]}]="Option 1"
# menulist[${#menulist[@]}]="2"
# menulist[${#menulist[@]}]="Option 2"

# This IFS environment change is what fixes the issue of spaces breaking the line from variables.
IFS=$'\n'
$DIALOG --title "Multiple Item list" \
--menu "Choose one" 15 55 5 \
$(printf "${menulist[*]}") 2>$local_dialogresponses

Dynamic Menus with Spaces in the elements

Daniel Pregler's picture

# ----- menu ----- #

options=(wipe erase delete "zap it")
i=0

for item in "${options[@]}"; do
array[i++]=$item
array[i++]=""
done

dialog --menu "Shall I ... ?" 11 30 ${#options[@]} "${array[@]}"

# ----- checklist ----- #

options=(wipe erase delete "zap it")
i=0; counter=0

for item in "${options[@]}"; do
(( counter++ ))
array[i++]=$counter
array[i++]=$item
array[i++]="off"
done

dialog --checklist "Shall I ... ?" 11 50 ${#options[@]} "${array[@]}"

Errors when running dialog --menu

Anonymous's picture

I tried running Listing 3 and Linux spit out tons of errors about "line 18: [: too many agruments" and on other lines, too. Is this because I have a (possibly) outdated dialog installation, or errors in the shell script?

Why the answers are not shown

Pty's picture

I would like to read the answers to the problems in this secction.

Need help on this topic

Sakthivel.C's picture

Hi,
I am using a alias command in the .bashrc in root as
alias hi="cd /mnt/tejas/software/"
so when i type hi in my konsole then it will move to software folder and there i am executing a shell script call ./manftest.sh whihc is a menu method os redirecting user to different path like
The manftest.sh shell will look like
1. software1
2. software2
3. software3
so if user selects option2 then he will moved to different path according to the choice.
but when i move a user like this the same path is not replicated on the title bar, i need the redirected path to be displayed in title bar so help me.

Re: Strictly On-Line: Linux Apprentice: Improve Bash Shell Scrip

Anonymous's picture

It's realy an usefull article ( and I am a beginer in this ) !!! Only the question remain - can/t find tutorials about - " How can I pass one menu to other without the window blinking ? I've tryed to understand info white pages , but it's not a such helpfull staff . "

Thanks in advance
Alex

Re: Strictly On-Line: Linux Apprentice: Improve Bash Shell Scrip

Anonymous's picture

It would be very nice if I could have more than one inputbox at the same window (like a form with various fields to fill in)...Is there any way to do that?

good idear

Tai's picture

which dialog 1.0
you can user the option: --form
good luck.

--form

Anonymous's picture

How to use --form. I tried it but got errors

Thanks

Looking for examples using

Anonymous's picture

Looking for examples using --form to use in dialog box. Can anyone help?
Thanks

Form

Anonymous's picture

The form idea is wonderful but how to read each field and save it in a variable --- Thanks for your extra help

Shell Script - Dialog Form Example

Nitin Soni's picture

dialog --backtitle "Dialog Form Example" --title "Dialog - Form" \
--form "\nDialog Sample Label and Values" 25 60 16 \
"Form Label 1:" 1 1 "Value 1" 1 25 25 30 \
"Form Label 2:" 2 1 "Value 2" 2 25 25 30 \
"Form Label 3:" 3 1 "Value 3" 3 25 25 30 \
"Form Label 4:" 4 1 "Value 4" 4 25 25 30 \
"Form Label 5:" 5 1 "Value 5" 5 25 25 30 \
"Form Label 6:" 6 1 "Value 6" 6 25 25 30 \
"Form Label 7:" 7 1 "Value 7" 7 25 25 30 \
"Form Label 8:" 8 1 "Value 8" 8 25 25 30 \
2>/tmp/form.$$

echo `cat /tmp/form.$$`
rm /tmp/form.$$

dialog - Form

Anonymous's picture

The form is great but how to save each field in a variable --- Thanks for your the help

Dialog - Form

Eduardo Yanaga's picture

dialog --backtitle "Dialog Form Example" --title "Dialog - Form" \
--form "\nDialog Sample Label and Values" 25 60 16 \
"Form Label 1:" 1 1 "Value 1" 1 25 25 30 \
"Form Label 2:" 2 1 "Value 2" 2 25 25 30 \
"Form Label 3:" 3 1 "Value 3" 3 25 25 30 \
"Form Label 4:" 4 1 "Value 4" 4 25 25 30 \
"Form Label 5:" 5 1 "Value 5" 5 25 25 30 \
"Form Label 6:" 6 1 "Value 6" 6 25 25 30 \
"Form Label 7:" 7 1 "Value 7" 7 25 25 30 \
"Form Label 8:" 8 1 "Value 8" 8 25 25 30 \
2>/tmp/form.$$

FORM=(`cat /tmp/form.$$ | tr '\\n' ' '`)

for i in 0 1 2 3 4 5 6 7;
do
echo "$FORM[$i]"
done

All the fiedls will be in the variable FORM.

Dialog - Form

Eduardo Yanaga's picture

dialog --backtitle "Dialog Form Example" --title "Dialog - Form" \
--form "\nDialog Sample Label and Values" 25 60 16 \
"Form Label 1:" 1 1 "Value 1" 1 25 25 30 \
"Form Label 2:" 2 1 "Value 2" 2 25 25 30 \
"Form Label 3:" 3 1 "Value 3" 3 25 25 30 \
"Form Label 4:" 4 1 "Value 4" 4 25 25 30 \
"Form Label 5:" 5 1 "Value 5" 5 25 25 30 \
"Form Label 6:" 6 1 "Value 6" 6 25 25 30 \
"Form Label 7:" 7 1 "Value 7" 7 25 25 30 \
"Form Label 8:" 8 1 "Value 8" 8 25 25 30 \
2>/tmp/form.$$

FORM=(`cat /tmp/form.$$ | tr '\\n' ' '`)

for i in 0 1 2 3 4 5 6 7;
do
echo "$FORM[$i]"
done

All the fiedls will be in the variable FORM.

Direct output to bash

Anonymous's picture

Direct output to bash variable:

exec 3>&1
FORM=$(dialog --backtitle "Dialog Form Example" --title "Dialog - Form" \
--form "\nDialog Sample Label and Values" 25 60 16 \
"Form Label 1:" 1 1 "Value 1" 1 25 25 30 \
"Form Label 2:" 2 1 "Value 2" 2 25 25 30 \
"Form Label 3:" 3 1 "Value 3" 3 25 25 30 \
"Form Label 4:" 4 1 "Value 4" 4 25 25 30 \
"Form Label 5:" 5 1 "Value 5" 5 25 25 30 \
"Form Label 6:" 6 1 "Value 6" 6 25 25 30 \
"Form Label 7:" 7 1 "Value 7" 7 25 25 30 \
"Form Label 8:" 8 1 "Value 8" 8 25 25 30 \
2>&1 1>&3)
exec 3>&-

echo $FORM

for i in 0 1 2 3 4 5 6 7;
do
echo "$FORM[$i]"
done

Ljubomir Ljubojevic

Webinar
One Click, Universal Protection: Implementing Centralized Security Policies on Linux Systems

As Linux continues to play an ever increasing role in corporate data centers and institutions, ensuring the integrity and protection of these systems must be a priority. With 60% of the world's websites and an increasing share of organization's mission-critical workloads running on Linux, failing to stop malware and other advanced threats on Linux can increasingly impact an organization's reputation and bottom line.

Learn More

Sponsored by Bit9

Webinar
Linux Backup and Recovery Webinar

Most companies incorporate backup procedures for critical data, which can be restored quickly if a loss occurs. However, fewer companies are prepared for catastrophic system failures, in which they lose all data, the entire operating system, applications, settings, patches and more, reducing their system(s) to “bare metal.” After all, before data can be restored to a system, there must be a system to restore it to.

In this one hour webinar, learn how to enhance your existing backup strategies for better disaster recovery preparedness using Storix System Backup Administrator (SBAdmin), a highly flexible bare-metal recovery solution for UNIX and Linux systems.

Learn More

Sponsored by Storix