Checking variable presence in bash script - bash

I'm retrieving a host-name from VMware Tools and trying to evaluate if two variables are present in my script:
selection=2
# Check if hostname is present in guestinfo
hostname="$(vmtoolsd --cmd "info-get guestinfo.startup.hostname")"
if [[ "$selection" = 2 && ! -z ${hostname+x} ]]
then
echo "Args present."
else
echo "Args NOT present."
fi
Regardless of whether hostname value is set in the VMX config file, the if statement returns "Args present."
I believe this is because the vmtoolsd command is executed, meaning the 'hostname' variable is not null. Unsure how to fix.
What is wrong?

First, clean up your tests-- don't use ! -z, when you have a -n.
In addition, if you are adding x to hostname, it will always be true (it will always return x by itself). Bash never needs the +x, get rid of it.
if [[ "$selection" = 2 && -n $hostname ]]; then
echo "Args present."
else
echo "Args NOT present."
fi

Related

Check if the parameter of a shell command exists

In my program, I have to check whether a command given as a input by a user exists or not and if it exists, program needs to check if the parameters of that command are correct.
For example:
ls ( is correct)
-al (is correct)
do the watch
and if I do this:
ls (is correct)
-kala (not correct)
don't do the watch.
How I can do this? Here is my script:
while true
do
echo "Insert the command"
read comm
if [ "$(type -t $comm)" != "" ]; then
echo "Insert the parameters of the command ";
read par;
echo "Insert the time of watch";
read time;
if [ $t -le 0 ]; then
echo "Value not correct";
else
clear;
while true
do
echo "$comm"
date
echo ""
$comm $par
sleep $((time))
clear
done
fi;
else
echo "Command not found, retry.";
echo "";
fi
done
You can replace the command invocation with this:
if ! $comm $par; then
exit 1
fi
to make it stop after an error. Also there is already a tool called watch but I think you already know this.

Shell Script not reading input

I have written a script that backs up and restores files. I have a problem in that when the user enters '2' for a restore the program says that this is an invalid input, all other options work fine. I feel it is something small that I have missed but I cant fix it
Update and Restore Script
#!/bin/bash
ROOT="/Users/Rory/Documents"
ROOT_EXCLUDE="--exclude=/dev --exclude=/proc --exclude=/sys --exclude=/temp --exclude=/run --exlucde=/mnt --exlcude=/media --exlude=/backup2.tgz"
DESTIN="/Users/Rory/test/"
BACKUP="backup2.tgz"
CREATE="/dev /proc /sys /temp /run /mnt /media "
if [ "$USER" != "root" ]; then
echo "You are not the root user"
echo "To use backup please use: sudo backup"
exit
fi
clear
echo "************************************************"
echo "********* Backup Menu **************************"
echo "************************************************"
OPTIONS="BACKUP RESTORE DESTINATION EXIT"
LIST="1)BACKUP 2)RESTORE 3)DESTINATION 4)EXIT"
select opt in $OPTIONS; do
if [ "$opt" = "EXIT" ]; then
echo "GOODBYE!"
sleep 3
clear
exit
elif [ "$opt" = "BACKUP" ]; then
echo "BACKING UP FILES..."
sleep 2
tar cvpfz $DESTIN/backup.`date +%d%m%y_%k:%M`.tgz $ROOT $ROOT_EXCLUDE_DIRS
echo "BACKUP COMPLETE"
sleep 2
exit
elif [ "$opt" = "RESTORE" ]; then
echo "RESTOTING FILES..."
sleep 2
tar xvpfz $BACKUP_FILE -C /
sleep2
echo "RESTORE COMPLETE..."
if [[ -e "/proc" ]]; then
echo "$CREATE_DIRS allready exists! "
else
mkdir $CREATE_DIRS
echo "$CREATE_DIRS are created! "
fi
exit
elif [ "$opt" = "DESTINATION" ]; then
echo "CURRENT DESTINATION: $DEST_DIR/backup.`date +%d/%m/%y_%k:%M`.tgz "
echo "TO CHANGE ENTER THE NEW DESTINATION..."
echo "TO LEAVE IT AS IS JUST PRESS ENTER..."
read NEW_DESTIN
#IF GREATER THEN 0 ASSIGN NEW DESTINATION
if [ ${#NEW_DESTIN} -gt 0 ]; then
DESTIN = "$NEW_DESTIN"
fi
clear
echo $BANNER1
echo $BANNER2
echo $BANNER3
echo $LIST
else
clear
echo "BAD INPUT!"
echo "ENTER 1 , 2, 3 or 4.."
echo $LIST
fi
done
Except where you missed the ending quote where you set ROOT_EXCLUDE (line #4), it looks okay to me. I take it the missing quote is a transcription error or your program wouldn't really work at all.
I've tried out the program and it seems to work.
A debugging trick is to put set -xv to turn on debugging in your script and set +xv to turn it off. The -x means to print out the line before executing, and the -v means to print out the line once the shell interpolates the line.
I'm sure that you'll immediately see the issue once you have set -xv in your program.
As part of this, you can set PS4 to the line prompt to print when the debugging information is printed. I like setting PS4 like this:
export PS4="[\$LINENO]> "
This way, the line prompt prints out the line it's executing which is nice.
In your case, I would put set -xv right before you set OPTIONS and then at the very end of the program. This way, you can see the if comparisons and maybe spot your issue.
export PS4="[\$LINENO]> "
set -xv
OPTIONS="BACKUP RESTORE DESTINATION EXIT"
LIST="1)BACKUP 2)RESTORE 3)DESTINATION 4)EXIT"
select opt in $OPTIONS; do
if [ "$opt" = "EXIT" ]; then
echo "GOODBYE!"
set +xv
By the way, it's better to use double square brackets like [[ ... ]] for testing rather than the single square brackets like [ ... ]. This has to do with the way the shell interpolates the values in the test.
The [ ... ] is an alias to the built in test command. The shell interpolates the line as is and the entire line is executed.
The [[ ... ]] are a compound statement where the shell will interpolate variables, but not the entire line. The line is kept as whole:
foo="one thing"
bar="another thing"
This will work:
if [ "$foo" = "$bar" ]
then
echo "Foo and bar are the same"
fi
This won't:
if [ $foo = $bar ]
then
echo "Foo and bar are the same"
fi
The shell interpolates the line as is:
if [ one thing = another thing ]
And this is the same as:
if test one thing = another thing
The test command looks at the first item to see if it's a standard test, or assumes three items and the second item is a comparison. In this case, neither is true.
However, this will work:
if [[ $foo = $bar ]] # Quotes aren't needed
then
echo "Foo and bar are the same"
fi
With the [[ ... ]] being a compound command, the $foo and $bar are replaced with their values, but their positions are kept. Thus, the = is recognized as a comparison operator.
Using [[ ... ]] instead of [ ... ] has solved a lot of hard to find shell scripting bugs I have.

Assigning the result of 'test' to a variable

I'm writing a script where I need to use the output of a file test in several places, including inside a shell function. I would like to assign the file existence to a shell variable, like this: file_exists=[ -f $myfile ].
Just to make sure that I've got my bases covered, I start by touching a file, and testing its existance:
file='a'
touch $file
if [ -f $file ]
then
echo "1 -- '$file' exists"
fi
Output:
1 -- 'a' exists
The file was created successfully -- no surprises, but at least I know that I'm not dealing with any permissions issues or anything.
Next I test to make sure that I can store a boolean expression in a variable:
mytest=/bin/true
if $mytest
then
echo "2 -- \$mytest is true"
fi
Output:
2 -- $mytest is true
So I've got the basics covered -- conditional expressions should emit the same output as /bin/true or /bin/false... but that's not what I'm seeing:
mytest=[ -f $file ]
if $mytest
then
echo "3 -- \$mytest is true [expect true]"
else
echo "3 -- \$mytest is false [expect true]"
fi
This fails with the following error:
-f: command not found
I get the same error message if i use test -f $file rather than [ -f $file ].
If I put a space in front of the [, the error goes away...
mytest= [ -f $file ]
if $mytest
then
echo "4 -- \$mytest is true [expect true]"
else
echo "4 -- \$mytest is false [expect true]"
fi
The output appears to be correct:
4 -- $mytest is true [expect true]
... but if I remove the file, I should get the opposite result:
rm $file
mytest= [ -f $file ]
if $mytest
then
echo "5 -- \$mytest is true [expect false]"
else
echo "5 -- \$mytest is false [expect false]"
fi
... and I don't:
5 -- $mytest is true [expect false]
To be fair, I expected the space to mess with the truth value:
mytest= /bin/false
if $mytest
then
echo "6 -- \$mytest is true [expect false]"
else
echo "6 -- \$mytest is false [expect false]"
fi
Outputs:
6 -- $mytest is true [expect false]
So, how do I store the output from the test builtin in a shell variable?
As others have documented here, using the string "true" is a red herring; this is not an appropriate way to store boolean values in shell scripts, as evaluating it means dynamically invoking a command rather than simply inspecting the stored value using shell builtins hardcoded in your script.
Instead, if you really must store an exit status, do so as a numeric value:
[ -f "$file" ] # run the test
result=$? # store the result
if (( result == 0 )); then # 0 is success
echo "success"
else # nonzero is failure
echo "failure"
fi
If compatibility with set -e is desired, replace the first two lines of the above with:
result=0
[ -f "$file" ] || result=$?
...as putting the test on the left-hand side of || marks it as "checked", suppressing errexit behavior. (That said, see BashFAQ #105 describing the extent to which set -e harms predictable, portable behavior; I strongly advise against its use).
You need to quote whitespace:
mytest='[ -f $file ]'
if $mytest; then echo yes; fi
However, this is extremely brittle and potentially insecure. See http://mywiki.wooledge.org/BashFAQ/050 for a detailed discussion and some better ways to accomplish something similar.
If you want to encapsulate a complex piece of code, a function is usually the way to go:
mytest () { [ -f "$file" ]; }
if mytest; then echo yes; fi
If you want to run the code once and store its result so you can examine it later, I would rephrase it like this:
if [ -f "$file" ]; then
mytest=true
else
mytest=false
fi
if $mytest; then echo yes; fi
A old one but left this here for reference for people that might need it. Not the most beautiful solution but it works in bash:
mytest=$( [ -f $file ] ; echo $? )
More portable, using the test command, and the backticks:
set mytest=`test -f $file ; echo $?`
In a subprocess (<!> system load), the condition is evaluated, and then the result echoed to the output that is captured by the variable $mytest.
mytest=/bin/true is storing the string /bin/true in the $mytest variable.
mytest=[ -f $file ] is setting the $mytest variable to the value [ for the duration of the command -f $file ] (which as your output indicates fails as there is no -f command available).
mytest= [ -f $file ] (like the above) sets the value of the $mytest variable to blank for the duration of the [ -f $file ] command (and returns whatever [ returns).
mytest= /bin/false this is the same as the above case only the command being run is /bin/false.
If you want to store the return code from a command in a variable you can do
/bin/true
ret=$?
if you want to store the output from a command in a variable you can do
out=$(/bin/true)
(though with /bin/true that variable will be empty as it outputs no text.
For your case you want the former $? model.
Also, using set -x (and/or set -v) in your scripts might have helped you diagnose this.
Different version with test command.
fileExists=$(test -f /path/to/file && echo true || echo false)
if [[ ${fileExists} == "true" ]]; then
# Your code here
fi
Or even more simplier version.
fileExists=$(test -f /path/to/file && echo 1)
if [[ -z ${fileExists} ]]; then
# Your code here
fi

unix construct "or" condition on a filename

I have a shell script where I pass (2) parameters, one to pass a dbname, the other to call one of (2) filenames. I want to check if either filename exists, then proceed with calling that script, else exit because the user can enter the wrong string and construct my_foo.sql which I don't want. I don't think I have the condition for setting "or" correctly since putting the correct param still produces error. Is there a better way to write this?
Here is what I have so far.
#/usr/bin/ksh
if [ $# != 2 ]; then
echo "Usage: test.sh <dbname> <test|live>" 2>&1
exit 1
fi
# Check actual file name
CHKSCRIPT1=/tmp/my_test.sql;
CHKSCRIPT2=/tmp/my_live.sql;
if [ -f "CHKSCRIPT1" ] || [ -f "CHKSCRIPT2" ]
then
/bin/sqlplus -s foo/bar #/my_$2.sql
else
echo "Correct sql script does not exist. Enter test or live"
exit 1
fi
Your issue is that you're not referencing your variables correctly:
if [ -f "$CHKSCRIPT1" ] || [ -f "$CHKSCRIPT2" ]
...
fi
edit: Per #chepner, you shouldn't use -o
In addition to the problem you had with expanding the parameters, you should separate what the user types from what files need to exist. If the user enters "live", the only thing that matters is whether or not /tmp/my_live.sql exists. If the user enters "injection_attack", your script should not execute /tmp/my_injection_attack.sql (which presumably was created without your knowledge). The right thing to do is to first verify that a valid command was entered, then check if the appropriate file exists.
if [ $# != 2 ]; then
echo "Usage: test.sh <dbname> <test|live>" 2>&1
exit 1
fi
case $2 in
test|live)
filename="/tmp/my_{$2}.sql"
;;
*) echo "Must enter test or live"
exit 1
;;
esac
if [ -f "$filename" ]; then
/bin/sqlplus -s foo/bar #/my_$2.sql
else
echo "SQL script $filename does not exist."
exit 1
fi

How do I test in a shell script whether I'm running inside Scratchbox2

In the old Scratchbox one could do something like:
if [ -f /targets/links/scratchbox.config ]; then
echo "here goes my sbox-dependent stuff"
fi
but what about Scratchbox2, is there a way to find out this?
How about test the environment variable 'LD_PRELOAD'
if [[ $LD_PRELOAD =~ "sb2" ]]; then
true # is running under sb2
fi

Resources