Click to See Complete Forum and Search --> : backtick usage in a function


dougd
03-04-2006, 02:09 PM
Currently I'm writing some test automation scripts in bash - I ask the user if he wants to run a specific test uing the following function

/snippet/
## This function takes asks if you want to run a given test. Pass the test name
## as the first arg and the test status variable name as the second arg.
function query_test_status {
printf "\nDo you want to run the $1 test? Enter y for yes or n for no: "
read $2
}
/end snippet/

In the actual control script it looks like

/snippet/
query_test_status HTTP HTTP_TEST_STATUS

while [[ $HTTP_TEST_STATUS != [YyNn] ]]
do
printf "\nYou didn't enter y or n.\nEnter y for yes or n for no: "
read HTTP_TEST_STATUS
done
/end snippet/

The while loop does input validation - I'm trying to add the input validation into the function call, for cleaner code and less typing; I have MANY test suites :-).

I am thinking of somehting like

/snippet/
function query_test_status {
printf "\nDo you want to run the $1 test? Enter y for yes or n for no: "
read $2
while [[ $`printf $2` != [YyNn] ]] ; then
do
printf "You didn't enter y or n.\nPlease enter y for yes or n for no: "
read $2
done
}

My problem is getting the backtick to create the proper variable name for the while loop.

Any ideas, or better solutions?

dougd
03-04-2006, 06:20 PM
So I've partly solved it...

/snippet/

#!/bin/bash

declare HTTP_TEST_STATUS

function query_test_status {
printf "Do you want to run the $1 test? Enter y for yes or n for no: "
local READ_REPLY
read READ_REPLY
while [[ $READ_REPLY != [YyNn] ]]
do
printf "You didn't enter y or n.\nPlease enter y or n: "
read READ_REPLY
done
$2=$READ_REPLY
}

/end snippet/

The last line *should* set HTTP_TEST_STATUS as equal to the answer taken by the read command - instead I get "HTTP_TEST_STATUS=y: command not found".

Anyone have a clue?

DSwain
03-04-2006, 06:26 PM
In bash you don't need to declare variables before the code is executed; Variables are assigned on-the-fly. I'm not sure, but don't you need to specify exactly what code the variable is suppose to execute?

dougd
03-04-2006, 06:32 PM
I pre-declare because all my scripts have shell option set to barf over undeclared variables - helps to catch misspellings. I just didn't include the opts in my snippet.

Regarding your second question - I don't quite understand your question - I've only been doing this a couple of weeks. Outside my function I want to set the variable HTTP_TEST_STATUS so I can use it later to determine which reports to run, etc. HTH and thanks for replying.

dougd
03-04-2006, 06:58 PM
Some more background...

In the actual script HTTP_TEST_STATUS is the variable I use to determine if the test will be run, and if the report should be generated later. Usage of the function looks like

query_test_status HTTP HTTP_TEST_STATUS

My goal is to pass the name of the test and the name of the test status variable to the function, and have the func ask the user if he wants to run the test, read the response, validate the input, and then assign the input to the test status variable to I can use it later when the tests are actually being run. I'm trying to make the function as generic as possible, so I get the most code re-use.

DSwain
03-04-2006, 11:19 PM
I don't know if it's possible, but can you show us the entire script? This might make it more clear as to what you're doing.

Reading through what you're saying though, I'm guessing that HTTP_TEST_STATUS is simply the choice (YyNn) and you want to pass that argument to the other function? If that's the case, you would probably wanna try something like this:

query_test_status $HTTP_TEST_STATUS

That should properly echo the value of HTTP_TEST_STATUS to the query_test_status variable. If I'm still misunderstanding, sorry haha.

dougd
03-05-2006, 01:33 AM
Here it is - I'm not trying to pass the value of HTTP_TEST_STATUS to the func - the value gets set BY the func.


#!/bin/bash

## This is the main script in the test suite suite.
##
## Doug Drew
## $CVS: $Header$

## Global Declarations

shopt -o -s nounset ## This helps catch undeclared variables.
declare -rx SCRIPT=${0##*/}

declare -rx mail="/bin/mail" ## Binary used to mail the test reports
declare -rx wget="/usr/bin/wget"
declare -rx ftp="/usr/bin/ftp"
declare -rx nslookup="/usr/bin/nslookup"
declare -rx mutt="/usr/bin/mutt"
declare -rx traceroute="/bin/traceroute"

declare INSTALL_DIR ## Location of install dir
declare SCRIPTS_DIR ## Location of scripts
declare CONFIGS_DIR ## Location of the config repository
declare RESULTS_DIR ## Base path for test results

## Variables to hold the location of the test config files and any
## download directories

declare HTTP_CONFIG_FILE
declare HTTP_RESULTS_DIR
declare FTP_CONFIG_FILE
declare FTP_RESULTS_DIR
declare DOMAIN_CONFIG_FILE
declare DOMAIN_RESULTS_DIR
declare POP3_CONFIG_FILE
declare POP3_RESULTS_DIR
declare ROUTE_CONFIG_FILE
declare ROUTE_RESULTS_DIR
declare LINK_CONFIG_FILE
declare LINK_RESULTS_DIR

## FUNCTIONS declarations section

## This function takes asks if you want to run a given test. Pass the test name
## as the first arg and the test status variable name as the second arg.
function query_test_status {
printf "\nDo you want to run the $1 test? Enter y for yes or n for no: "
read $2
}


## The following variables store the availability status for the binaries
## used in each of the tests.

## declare MAIL_BIN_STATUS
## declare HTTP_BIN_STATUS
## declare FTP_BIN_STATUS
## declare DOMAIN_BIN_STATUS
## declare POP3_BIN_STATUS

## Set the config files to the actual files and paths

cd .. ## Move to the top dir to create starting path variable
INSTALL_DIR=$PWD
SCRIPTS_DIR=$INSTALL_DIR"/scripts/"

cd $SCRIPTS_DIR ## Move back to the scripts dir

CONFIGS_DIR=$INSTALL_DIR"/config_files/"
RESULTS_DIR=$INSTALL_DIR"/test_results/"
HTTP_CONFIG_FILE=$CONFIGS_DIR"HTTP_config"
HTTP_RESULTS_DIR=$RESULTS_DIR"HTTP_test/"
FTP_CONFIG_FILE=$CONFIGS_DIR"FTP_config"
FTP_RESULTS_DIR=$RESULTS_DIR"FTP_test/"
DOMAIN_CONFIG_FILE=$CONFIGS_DIR"DOMAIN_config"
DOMAIN_RESULTS_DIR=$RESULTS_DIR"DOMAIN_test/"
POP3_CONFIG_FILE=$CONFIGS_DIR"POP3_config"
POP3_RESULTS_DIR=$RESULTS_DIR"POP3_test/"
ROUTE_CONFIG_FILE=$CONFIGS_DIR"ROUTE_config"
ROUTE_RESULTS_DIR=$RESULTS_DIR"ROUTE_test/"
LINK_CONFIG_FILE=$CONFIGS_DIR"LINK_config"
LINK_RESULTS_DIR=$RESULTS_DIR"LINK_test/"

## Sanity Checks Section
## First we perform tests to make sure that all required external binaries
## are present.
## Currently, we exit if ANY of the binaries are missing -
## conditional code to be implemented after the user interaction
## code determines which bins are required for the requested tests.

if test ! -x "$mail" ; then
## MAIL_BIN_STATUS="n" ;
printf "$SCRIPT:$LINENO: the $mail command is not available!"
exit 192
fi

if test ! -x "$wget" ; then
## HTTP_BIN_STATUS="n" ;
printf "$SCRIPT:$LINENO: the $wget command is not available!"
exit 192
fi

if test ! -x "$ftp" ; then
## FTP_BIN_STATUS="n" ;
printf "$SCRIPT:$LINENO: the $ftp command is not available!"
exit 192
fi

if test ! -x "$nslookup" ; then
## DOMAIN_BIN_STATUS="n" ;
printf "$SCRIPT:$LINENO: the $nslookup command is not available!"
exit 192
fi

if test ! -x "$mutt" ; then
## POP3_BIN_STATUS="n" ;
printf ":$SCRIPT:$LINENO: the $mutt command is not available!"
exit 192
fi

## Now we check to see if all the config files are present. This allows
## us to do our conditional checking for resource availability and
## which test suites are running - not currently in use

## declare HTTP_CONFIG_STATUS
## declare FTP_CONFIG_STATUS
## declare DOMAIN_CONFIG_STATUS
## declare POP3_CONFIG_STATUS

##if test -e "$CONFIG_DIR_LOCATION""$HTTP_CONFIG_FILE" ; then
## HTTP_CONFIG_STATUS="y"
##fi

## Main workflow section - user interaction begins here.
## This section determines which tests will be run on this iteration of
## the test.

printf "\nWelcome to DD's interop test script.

sleep 3

declare HTTP_TEST_STATUS
declare FTP_TEST_STATUS
declare DOMAIN_TEST_STATUS
declare POP3_TEST_STATUS
declare ROUTE_TEST_STATUS
declare LINK_TEST_STATUS
declare REPORT_OVERWRITE_STATUS

## Do some sanity checking and see if master_report.txt exists and allow the
## user to exit if he doesn't want to overwrite it.

declare MASTER_REPORT
MASTER_REPORT=$RESULTS_DIR"master_report.txt"

if test -e $MASTER_REPORT ; then
printf "WARNING: The master report file already exists!\n\nDo you want to continue and overwrite the file?\n\nPlease enter y to continue or n to abort the test run: "
read REPORT_OVERWRITE_STATUS

while [[ $REPORT_OVERWRITE_STATUS != [YyNn] ]]
do
printf "You didn't enter y or n.\nPlease enter y to continue or n to abort the test run: "
read REPORT_OVERWRITE_STATUS
done

if [[ $REPORT_OVERWRITE_STATUS = [Yy] ]] ; then
cd $RESULTS_DIR
rm -f master_report.txt
cd $HTTP_RESULTS_DIR
rm -f *
cd $FTP_RESULTS_DIR
rm -f *
cd $DOMAIN_RESULTS_DIR
rm -f *
cd $ROUTE_RESULTS_DIR
rm -f *
cd $LINK_RESULTS_DIR
rm -f *
cd $SCRIPTS_DIR
else
printf "\nAborting test now!!!\n\n"
exit 192
fi
fi

## Determine if a copy of the report should be mailed
declare REPORT_MAIL_STATUS
declare REPORT_RECIPIENT

printf "Do you want to mail a copy of the report to a local mail account?\n\nEnter y for yes and n for no: "
read REPORT_MAIL_STATUS

while [[ $REPORT_MAIL_STATUS != [YyNn] ]]
do
printf "You didn't enter y or n.\nPlease enter y of yes or n for no: "
read REPORT_MAIL_STATUS
done

if [[ $REPORT_MAIL_STATUS = [Yy] ]] ; then
printf "Enter the local account who should receive the report: "
read REPORT_RECIPIENT
printf "\nThe master report will be created in $RESULTS_DIR, and a copy will be mailed to $REPORT_RECIPIENT.\n\n"
else
printf "\nThe master report will be created in: \n$RESULTS_DIR\n\n"
fi

## Determine which tests will be run
query_test_status HTTP HTTP_TEST_STATUS
while [[ $HTTP_TEST_STATUS != [YyNn] ]]
do
printf "\nYou didn't enter y or n.\nEnter y for yes or n for no: "
read HTTP_TEST_STATUS
done

query_test_status FTP FTP_TEST_STATUS
while [[ $FTP_TEST_STATUS != [YyNn] ]]
do
printf "\nYou didn't enter y or n.\nEnter y for yes or n for no: "
read FTP_TEST_STATUS
done

query_test_status DOMAIN DOMAIN_TEST_STATUS
while [[ $DOMAIN_TEST_STATUS != [YyNn] ]]
do
printf "\nYou didn't enter y or n.\nEnter y for yes or n for no: "
read DOMAIN_TEST_STATUS
done

query_test_status ROUTE ROUTE_TEST_STATUS
while [[ $ROUTE_TEST_STATUS != [YyNn] ]]
do
printf "\nYou didn't enter y or n.\nEnter y for yes or n for no: "
read ROUTE_TEST_STATUS
done

##query_test_status LINK LINK_TEST_STATUS
##while [[ $LINK_TEST_STATUS != [YyNn] ]]
##do
## printf "\nYou didn't enter y or n.\nEnter y for yes or n for no: "
## read LINK_TEST_STATUS
##done

## Run tests now
printf "\nThe selected test series will begin in 3 seconds\n\nPress Ctrl-C to abort the tests!\n"
sleep 3
printf "Beginning tests now...\n\n"


## HTTP test section
if [[ $HTTP_TEST_STATUS = [Yy] ]] ; then
printf "HTTP test run beginning now.\n"
$wget -o $HTTP_RESULTS_DIR"testlog.txt" -P $HTTP_RESULTS_DIR -i $HTTP_CONFIG_FILE
sleep 5
fi

## FTP test section
if [[ $FTP_TEST_STATUS = [Yy] ]] ; then
printf "FTP test run beginning now.\n"
cd $FTP_RESULTS_DIR ## Move to the proper dir for ftp
source $FTP_CONFIG_FILE
## Make sure that the upload file exists
if test ! -e $CONFIGS_DIR$FTP_PUT_FILE ; then
printf "$CONFIGS_DIR$FTP_PUT_FILE doesn't exist! Please create it and try again. Aborting!"
exit 192
fi

cp $CONFIGS_DIR$FTP_PUT_FILE $FTP_PUT_FILE ## Create local copy
$ftp -n -i -v 1&> testlog.txt << End-of-Session
open $FTP_SITE_URL
user $FTP_USER $FTP_PASSWORD
bin
get $FTP_GET_FILE
put $FTP_PUT_FILE
bye

End-of-Session
cd $SCRIPTS_DIR
fi

## DOMAIN test section
declare TEST_DOMAIN ## Variable to hold info in for loop
if [[ $DOMAIN_TEST_STATUS = [Yy] ]] ; then
printf "DOMAIN test run beginning now.\n"
for TEST_DOMAIN in $(cat $DOMAIN_CONFIG_FILE)
do
$nslookup $TEST_DOMAIN >> $DOMAIN_RESULTS_DIR"testlog.txt"
done
fi

## ROUTE test status
if [[ $ROUTE_TEST_STATUS = [Yy] ]] ; then
printf "ROUTE test run beginning now.\n"
source $ROUTE_CONFIG_FILE
$traceroute $REMOTE_TEST_HOST > $ROUTE_RESULTS_DIR"testlog.txt"
fi

## LINK test section
##if [[ $LINK_TEST_STATUS = [Yy] ]] ; then
## printf "LINK test run beginning now.\n"
## source $LINK_CONFIG_FILE


## Reporting section will concatenate all the test logs into a master report,
## including the error log(s).


printf "Master report is being generated now.\n"
sleep 5

printf "This is the interop test master report from `date`.\n\n" > $MASTER_REPORT

## Append the HTTP test report to the master report if applicable
if [[ $HTTP_TEST_STATUS = [Yy] ]] ; then
printf "\n*****HTTP test report*****\n\n" >> $MASTER_REPORT
cat $HTTP_RESULTS_DIR"testlog.txt" >> $MASTER_REPORT
else
printf "\nHTTP report not included because it wasn't run!\n" >> $MASTER_REPORT
fi

## Append the FTP test report to the master report if applicable
if [[ $FTP_TEST_STATUS = [Yy] ]] ; then
printf "\n\n*****FTP report section*****\n\n" >> $MASTER_REPORT
cat $FTP_RESULTS_DIR"testlog.txt" >> $MASTER_REPORT
else
printf "\nFTP report not included because it wasn't run!\n" >> $MASTER_REPORT
fi

## Append the DOMAIN test report to the master report if applicable
if [[ $DOMAIN_TEST_STATUS = [Yy] ]] ; then
printf "\n\n*****DOMAIN report section*****\n\n" >> $MASTER_REPORT
cat $DOMAIN_RESULTS_DIR"testlog.txt" >> $MASTER_REPORT
else
printf "\nDOMAIN report not included because it wasn't run!\n" >> $MASTER_REPORT
fi

## Append the ROUTE test report to the master report if applicable
if [[ $ROUTE_TEST_STATUS = [Yy] ]] ; then
printf "\n\n*****ROUTE report section*****\n\n" >> $MASTER_REPORT
cat $ROUTE_RESULTS_DIR"testlog.txt" >> $MASTER_REPORT
else
printf "\nROUTE report not included because it wasn't run!\n" >>$MASTER_REPORT
fi

## Print end of report

printf "\n*****END OF REPORT*****\n" >> $MASTER_REPORT

## Mail the report if applicable

if [[ $REPORT_MAIL_STATUS = [Yy] ]] ; then
mail $REPORT_RECIPIENT -s "Interop Test Master Report" < $MASTER_REPORT
printf "Report mailed to $REPORT_RECIPIENT.\n"
fi

exit 0 ## Script finished normally

bwkaz
03-05-2006, 03:06 PM
instead I get "HTTP_TEST_STATUS=y: command not found". That's because shell variable substitution happens after variable assignment, at least in this case. It might be possible to get it to work by putting an "export" before the $2, but I wouldn't be surprised if it still breaks.

I'm not sure what you're asking for is possible at all, in other words.

What you can do, however, is something like this (oh, and note the code tags; they make it so that indentation is preserved; we can read your code better when you use them ;)):

function query_test_status {
printf "Do you want to run the $1 test? Enter y for yes or n for no: "

local READ_REPLY
read READ_REPLY

# Also note: You can't check against a glob or regex using the "test" or "["
# builtins. You have to use "case" to do it. Also note that ":" is short for
# /bin/true (i.e. always succeed), but doesn't waste a process:

while : ; do
case $READ_REPLY in
[YyNn])
break
;;
*)
printf "You didn't enter y or n.\nPlease enter y or n: "
read READ_REPLY
;;
esac
done

echo "$READ_REPLY"
} Then, call it like this:

HTTP_TEST_STATUS=`query_test_status HTTP`

dougd
03-05-2006, 03:31 PM
All, thanks for the insight to a VERY green script writer. I'll give the sample a shot and let everyone know. bwkaz, thanks for the tip about the "code" tags too!

dougd
03-05-2006, 03:50 PM
All, thanks for the insight to a VERY green script writer. I'll give the sample a shot and let everyone know.

bwkaz, thanks for the tip about the "code" tags too - how exactly do I use them?

I've tried out the sample from bwkaz, and my shell prompt doesn't return so I must have some error in the transcription - I'll fiddle and post what I get to work.

FYI regex was working in my sample. Regex doesn't work with "[]" but it seems to work OK with "[[]]".

bwkaz
03-05-2006, 10:56 PM
Didn't work with my shell (bash 3.00.16(1)-release):

$ [[ 'X' != '[YyNn]' ]] && echo "not equal"
not equal
$ [[ 'Y' != '[YyNn]' ]] && echo "not equal"
not equal
$ As you can see, neither X nor Y match the glob. X should not, but Y should. (The $ is my prompt; in case you want to run this in your shell, don't copy the $ characters.)

dougd
03-05-2006, 11:20 PM
Odd...
if [[ Y != [YnNn] ]] ; then printf "Its not a match" ; else printf "Its a match" ; fi
if [[ X != [YnNn] ]] ; then printf "Its not a match" ; else printf "Its a match" ; fi

Works as expected for globing (bash version 3.1.7).

Having said that...I am still fighting w/ the case statement - I've never used it before, and am still learning :-). Thanks again, and I'll post my final code in case anyone finds it useful.

Speaking of...how do i indicate the "code" tags...I'd like to get it right in the future

DSwain
03-05-2006, 11:45 PM
The code tag can be used like this: [ CODE ] actual code [ / CODE ] only remove the spaces from within the brackets.

Sorry I couldn't be of anymore help with your code. I'm not really sure what you're asking for entirely either, and bwkaz is by far a better person to ask for those types of questions.

bwkaz
03-06-2006, 07:32 PM
Hmm, it appears to have something to do with the quoting. If I don't quote the [YyNn] "word", then it matches when it's supposed to. If I do quote it, then it never matches.

Learn something new every day. ;)

OK, since that works, forget case. Just use [[ instead. :) (As long as you don't plan on running this under any shell other than bash. The [[ ... ]] construct may be a bash-ism, not supported under other Bourne compatible shells. Not sure whether it actually is or not, though.)