LinuxQuestions.org
LinuxAnswers - the LQ Linux tutorial section.
Go Back   LinuxQuestions.org > Articles > Technical
User Name
Password

Notices

By stomfi at 2006-02-14 03:57
Power to the Users. Shell Scripting & GUI interfacing for Desktop Users

By Stomfi © 2006


Part 4 - Validation, Editing and Dialog Windowing

Validation

Adding validation to input scripts is used to make sure the user doesn't enter something unwanted. Simple validation was already used in the while loops to make sure the user entered at least one character when the names were input with the test construct:

Code:
[ ${#Varname} -lt 1 ]
which tests for the number of characters in Varname, and returns "true" if there are none.

The simple input script doesn't test that the user entered only one name, which is what we want, although it could be a hyphenated name, which is OK.

An important decision when dealing with validations, is to decide what the script should do if there is an input error.

The easiest is to continue the loop without any error message relying on the returning loop's screen message to help the user decide that they made a mistake. If the user entered more than one word, which would contain an embedded space, the validation construct could look like this:

Code:
while [ ${#Varname} -lt 1 ] 
do
     echo "Enter a single word"
     read Varname
     if [ ${#Varname -lt 1 ]
     then
          continue
     fi
     #If it gets to here there must be some characters in Varname
     TESTWORDS=`echo $Varname | wc -w`
     if [ $TESTWORDS -gt 1 ]
     then
         continue
     fi
done
The second test checks that TESTWORDS is not greater than one for the script to continue on. The "wc -w" command gives a word count of the number of words in the echoed variable.

So what happens if the user inputs a number instead of letters by mistake. The shell treats numbers and letters the same unless a mathematical action is performed. If you multiplied a string of characters by 1, the answer will be 0, whereas any number greater than 0 will give a number greater than 0.

A simple test for number that can be inserted in the above script, with an error message could look like this:

Code:
let TESTNUMBERS=$Varname*1
if [ $TESTNUMBERS -gt 0 ]
then
      #Tell the user what they did
      echo "Enter only letters. $Varname is a number"
      #Don't continue until they have seen the error
      echo "Press Enter to Continue"
      read
      continue
fi
You can see that validation is difficult, as in the above example we don't check for the number zero. This can be included as a specific test as in "[ $Varname -eq 0 ]" performed before the Numbers test.

In a multi input script, writing the same tests for each variable name makes the script long and unwieldy. Functions that can be called as in "Oneword "$Varname"" are much better. A function to validate for unwanted numbers could be written once at the beginning of the script and called whenever it is required.
Code:
function Notanumber{}
{
    #Set error flag to no error
     ERR=0
    #Set an error message
    ERRMSG="NONE" 
     if [ $1 -eq 0 ]
    then
         #Set error flag to an error
         ERR=1
    fi
    if [ $ERR -eq 0 ]
    then
        let TESTNUMBERS=$1*1
        if [ $TESTNUMBERS -gt 0 ]
        then
            ERR=1
       fi
    fi
    if [ $ERR -gt 0 ]
    then
       ERRMSG="Enter only letters. $1 is a number"
    fi
}
In the script the validator can be called and tested for ERRMSG not equal to NONE

Code:
Notanumber "$Varname"
if [ $ERRMSG != "NONE" ]
then
      echo "$ERRMSG"
      echo "Press Enter to Continue"
      read
fi
There are many ways that validation functions can be written. These ones are designed to make it obvious what they are doing so that you can understand how such things work.

Validating new input against existing records

In the namesrec scripts a entry where FIRSTNAME and LASTNAME are the same as a record in the namesrec file can be considered a duplicate entry for our purposes. So a validator would be inserted before the record was written to the file to test for an existing record line like this.

Code:
EXISTS=`grep "$FIRSTNAME#$LASTNAME" $NRECS`
if [ ${#EXISTS} -gt 1 ]
then
      echo "$FIRSTNAME $LASTNAME already in the records"
else
     #Append the fields each delimited by a #, to the records file
     echo "$LINECOUNT#$FIRSTNAME#$LASTNAME#$REFNUMBER#$EDATE" >> $NRECS
fi
Grep globally searches for regular expressions and prints the result. If it doesn't find one in this case, it writes the record.

Edit existing records

Using the validation for EXISTS, the rest of the details for an existing record can be displayed in the Tput enhanced screen after LASTNAME is entered. This is done by setting the variable values from the record line returned by the grep command, which are in the EXISTS variable.

In the input script after LASTNAME has been collected and set to upper case the following lines will replace all the fields on the screen with the values from the record.

Code:
#Set a record edit flag to no
REDIT=0
EXISTS=`grep "$FIRSTNAME#$LASTNAME" $NRECS`
if [ ${#EXISTS} -gt 1 ]
then
      LINECOUNT=`echo $EXISTS | cut -d"#" -f1`
      FIRSTNAME=`echo $EXISTS | cut -d"#" -f1`
      LASTNAME=`echo $EXISTS | cut -d"#" -f1`
      REFNUMBER=`echo $EXISTS | cut -d"#" -f1`
      EDATE=`echo $EXISTS | cut -d"#" -f1`
      #Repaint the screen with the retrieved values
      Screenpaint
      #Set the record edit flag to yes so that the record is replaced  
      REDIT=1
      #Put the cursor onto an edit field. This normally would be the next field after LASTNAME
      tput cup 8 30
fi
Pressing Enter on the already filled fields will accept the values, or they can be over written with new ones.
The validation that checks for an existing record will need changing so that the edited record can be replaced.
Grep is used with "-v" which inverts the match printing lines that don't match the regular expression.

Code:
if [ $REDIT -gt 0 ]
then
     #Is a replacement record entry
     #Echo all the lines except this one into a temporary file
     grep -v "$FIRSTNAME#$LASTNAME" $NRECS >  $NRECDIR/temprec.txt
     #Add the edited record at the end
     echo "$LINECOUNT#$FIRSTNAME#$LASTNAME#$REFNUMBER#$EDATE" >> $NRECDIR/temprec.txt
     #Sort the temporary file on the LINECOUNT field and replace the NRECS file with the result
     sort -t# -k 1,1 $NRECDIR/temprec.txt > $NRECS
     #Reset the edit flag so the next record entry is processed correctly
     REDIT=0
else
     #Append the fields, each delimited by a #, to the records file
     echo "$LINECOUNT#$FIRSTNAME#$LASTNAME#$REFNUMBER#$EDATE" >> $NRECS
fi
All the scripting you have learned and practiced thus far will be used in the GUI project, but before I take you down that interesting path, I first want to show you how you can enhance shell based projects with "dialog" which you should have set up on your system by following the instructions at the end of part 3 of these articles.

In case you missed out.

Before you can use ?dialog? in your scripts you have to have a ?.dialogrc file in your home folder. Create this with the command:

Code:
dialog ?create-rc $HOME/.dialogrc
Luckily for all of us, the dialog sources come with examples which in my case, I copied and modified, and included some comments to make things a bit clearer. These scripts are from a real system I wrote for a small warehouse operation where a full GUI system was overkill. This allows the system to run on recycled 386/486 machines, and send the results of each transaction to a central server, all for very little cost.

Some of the script lines you may find a bit difficult to understand,, but if you use the manual pages for each command you may be able to get the message. I am not going to explain anything in depth as we won't be using anything but the real simple stuff in the Storyboard project, and will probably never use dialog again, unless of course you have a need for it like I did.

Picture of a dialog menu screen

This is a script for a goods menu.

Code:
#!/bin/sh
# $Id: menubox,v 1.4 2003/08/15 19:40:37 tom Exp $
#Colon does nothing and the rest sets a variable for the dialog program
: ${DIALOG=dialog}
#This line sets a path to tempfile. The $$ is the current shell ID.
tempfile=`tempfile 2>/dev/null` || tempfile=/tmp/test$$
#This uses trap. This line makes sure the tempfile is deleted
trap "rm -f $tempfile" 0 1 2 5 15
#This is the dialog script. Each line, except the last line, is terminated by backslash, which means
#treat the script as being all on one line.
#
$DIALOG --clear --title "GOODS SYSTEM" \
--menu "You can use the UP/DOWN arrow keys, the first \n\
letter of the choice as a hot key, or the \n\
number keys 1-2 to choose an option.\n\n\
Choose a Goods System Item:" 20 51 4 \
"Inwards" "Inwards Goods"\
"Outwards" "Outwards Goods" 2> $tempfile
#
#The standard error (file descriptor 2) is directed to tempfile.
#Here is another new construct. The bash man page states that $? expands to the
#status of the most recently executed foreground pipeline, in this case the dialog.

retval=$?

#tempfile is going to contain the choice from the list of displayed menu options
choice=`cat $tempfile`

#A case and a sub case let us perform the desired choice or exit the program
case $retval in
          0) case "$choice" in
                   "Inwards") $HOME/GOODS/bin/inwards;;
                   "Outwards") $HOME/GOODS/bin/outwards;;
              esac;;
          1) exit;;
      255) exit;;
esac
exit
The two sub menus are very similar except they exit back to the calling script. Here is one of them.


Code:
#!/bin/sh
# $Id: menubox,v 1.4 2003/08/15 19:40:37 tom Exp $
: ${DIALOG=dialog}
tempfile=`tempfile 2>/dev/null` || tempfile=/tmp/test$$
trap "rm -f $tempfile" 0 1 2 5 15

$DIALOG --clear --title "INWARDS GOODS SYSTEM" \
--menu "You can use the UP/DOWN arrow keys, the first \n\
letter of the choice as a hot key, or the \n\
number keys 1-2 to choose an option.\n\n\
Choose a Goods System Item:" 20 51 4 \
"Entry" "Enter New Inwards Goods"\
"Adjust" "Adjust Existing Inwards Goods Entry" 2> $tempfile

retval=$?
choice=`cat $tempfile`
case $retval in
           0) case "$choice" in
                       "Entry") $HOME/GOODS/bin/inentry ;;
                       ?Adjust?) $HOME/GOODS/bin/inadjust ;;
               esac ;;
          1) $HOME/GOODS/bin/goods;;
      255) $HOME/GOODS/bin/goods;;
esac
The entry screens are quite a bit different as they allow the arrow keys to be used to navigate.

Picture of a dialog entry screen.

The submit button is clicked when the record is complete. This is the script.

Code:
#! /bin/sh
# $Id: form1,v 1.6 2004/03/13 16:06:51 tom Exp $
: ${DIALOG=dialog}
#Initialise variables
EDATE=`date +%d/%m/%y`
DOCNUM=""
SUPPCODE=""
CARRIER=""
GOODSCODE=""
GOODSNAME=""
GQUANT=""
GMEAS=""
RECDBY=""

#Sets the top title
backtitle="New Inwards Goods Entry Form"
#Set the return code
returncode=0
#This script uses the word test which is the same as its alias brackets [ ]
while test $returncode != 1 && test $returncode != 250
do
    #Exec means replace this shell with the following command. The following argument
    #concatenates file descriptor 3 (the current file) and file descriptor 1 (the standard output)
    exec 3>&1
    value="`$DIALOG --ok-label "Submit" \
    --backtitle "$backtitle" \
    --form "Enter values in all empty fields" \
    20 50 0 \
    "Date:" 1 1 "$EDATE" 1 10 10 0 \
    "DOCNUM:" 2 1 "$DOCNUM" 2 10 8 0 \
    "SUPPCODE:" 3 1 "$SUPPCODE" 3 10 8 0 \
    "CARRIER:" 4 1 "$CARRIER" 4 10 30 0 \
     "GOODSCODE:" 5 1 "$GOODSCODE" 5 10 8 0 \
     "GOODSNAME:" 6 1 "$GOODSNAME" 6 10 40 0 \
     "GQUANT:" 7 1 "$GQUANT" 7 10 6 0 \
     "GMEAS:" 8 1 "$GMEAS" 8 10 10 0 \
     "RECDBY:" 9 1 "$RECDBY" 9 10 20 0 \
     2>&1 1>&3`"

     returncode=$?
     #This one concatenates the current file with the input
     exec 3>&-
     show=`echo "$value" |sed -e 's/^/ /'`
     #Some more dialogs
     case $returncode in
              1) "$DIALOG" \
                    --clear \
                    --backtitle "$backtitle" \
                    --yesno "Really quit?" 10 30
                   case $? in
                            0) break ;;
                            1) returncode=99 ;;
                   esac ;;
              0) $DIALOG --title "POST THIS RECORD ENTRY?" \
                     --yesno "$show" 15 40
                    case $? in
                           0) #Check that all fields are filled before writing record, or give an error message
                               #Create the record string from $value, deleting the last #
                               NRECORD=`echo "$value"|awk 'BEGIN{ORS="#"}{print $0}'|sed -e 's/#$//'`
                               #Count the number of fields
                               NUMFLDS=`echo ?$NRECORD? | awk -F?#? 'END{print NF}'`
                               if [ $NUMFLDS -lt 9 ]
                               then
                                     $DIALOG --title "INPUT ERROR" --clear \
                                     --msgbox "You must fill in all the fields.\n\
                                     This record will not be saved" 10 41
                                   case $? in
                                             0) $HOME/GOODS/bin/inwards ;;
                                         255) $HOME/GOODS/bin/inwards ;;
                                   esac
                               else
                                   echo $NRECORD >> $HOME/GOODS/data/ingoods.txt
                               fi ;;
                           1) $HOME/GOODS/bin/inwards ;;
                       255) $HOME/GOODS/bin/inwards ;;
                   esac;;
              *) $HOME/GOODS/bin/inwards ;;
    esac
done
$HOME/GOODS/bin/inwards

This script includes three different types of dialog scripts, a form, a yesno, and a msgbox. This and the tput script should give you a very good idea how to use dialog and the shell to create very sophisticated dialogs.

In part 5 the Runtime Revolution interface will be described. I use version 2.2.1, but 1.1.0 is quite adequate as most of the work is done by shell scripts like we have been practicing up to this point.


  



All times are GMT -5. The time now is 05:40 PM.

Main Menu
Advertisement

Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
identi.ca: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration