Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
If you have any problems with the registration process or your account login, please contact us. If you need to reset your password, click here.
Having a problem logging in? Please visit this page to clear all LQ-related cookies.
Get a virtual cloud desktop with the Linux distro that you want in less than five minutes with Shells! With over 10 pre-installed distros to choose from, the worry-free installation life is here! Whether you are a digital nomad or just looking for flexibility, Shells can put your Linux machine on the device that you want to use.
Exclusive for LQ members, get up to 45% off per month. Click here for more info.
By stomfi at 2006-02-14 03:57
Power to the Users. Shell Scripting & GUI interfacing for Desktop Users
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.
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.