Bash Script with if, elif, else - bash

Okay so this is an assignment so I will not put in the exact script here but I am really desperate at this point because I cannot figure something as basic as if's. So I am basically checking if the two arguments that are written in the command line are appropriate (user needs to type it correctly) or it will echo a specific error message. However, when I put in a command with 100% correct arguments, I get the error echo message from the first conditional ALWAYS (even if I switch around the conditional statements). It seems that the script just runs the first echo and stops no matter what. Please help and I understand it might be hard since my code is more of a skeleton.
if [ ... ]; then
echo "blah"
elif [ ... ]; then
echo "blah2"
else for file; do
#change file to the 1st argument
done
fi
I obviously need the last else to happen in order for my script to actually serve its intended purpose. However, my if-fy problem is getting in the way. The if and elif need to return false in order for the script to run for appropriate arguments. The if and elif check to see if the person typed in the command line correctly.

elif mean else-if. So it only will only be checked if the first statement returns false. So if you want to check if both are correct do.
if [ ... ] then
...
fi
if [ ... ] then
...
fi

When you care about checking both the first and second command line arguments for a single condition (i.e. they must both meet a set of criteria for the condition to be true), then you will need a compound test construct like:
if [ "$1" = somestring -a "$2" = somethingelse ]; then
do whatever
fi
which can also be written
if [ "$1" = somestring ] && [ "$2" = somethingelse ]; then
...
Note: the [ .... -a .... ] syntax is still supported, but it is recommended to use the [ .... ] && [ .... ] syntax for new development.
You can also vary the way they are tested (either true/false) by using -o for an OR condition or || in the second form. You can further vary your test using different test expressions (i.e. =, !=, -gt, etc..)

Related

grouping and simplifying multiple if conditions / bash

I have multiple If conditions to to run at the beginning of the script
if blah; then
echo "hop"
fi
if [ ! -f blah-blah ]; then
echo "hop-hop"
else
echo "hip-hop-hap"
fi
if [ $? -eq 0 ]; then
echo "hip-hop"
fi
each of this conditions
are separate from each other, and I have them 7-8
so I'm thinking if there is some way to group them...
I was thinking to use
elif , but elif will stop checking conditions if one of them is truth,
any suggestion would be ppreciated
If what you are hoping for is shorter code, you could do something like this :
blah && echo "hop"
[ -f "blah-blah" ] && echo "hip-hop-hap" || echo "hop-hop"
[ $? = 0 ] && echo "hip-hop"
This is not "simpler" in the logical sense, but it is more concise.
Please note that I removed the ! from the test and switched the resulting statements as a small optimization.
Please note, however, that if you want to perform any kind of error checking or explicit handling (i.e. trap ... ERR, set -e), then using logical operators is going to interfere with that and you will not be able to tell the difference between a bug in your script and a command that fails for "good reasons" (i.e. attempting to delete a non-existing file). You are probably mostly safe if you restrict yourself to echo statements, or if, like most shell programmers, you allow the shell to simply ignore failed statements (which is not, in my opinion, a good way to build predictable and reliable shell code).
It is hard to give a definitive answer to a question that asks for some way.
Here is an alternative idea. We create an array of boolean values that contains the results of evaluation of logical conditions. And for each
condition i define a pair of functions func${i}0 and func${i}1 that will be called when condition i evaluates to true or false respectively. Finally we loop through our boolean array.
The code below implements this idea. It is rather awkward. Feel free to suggest improvements or downvote.
Here we assume that blah evaluates to either 0 or 1.
# populate array b by evaluating boolean conditions
foo=$( blah ); b[1]=$?
foo=$( [ ! -f blah-blah ] ); b[2]=$?
foo=$( [ $? -eq 0 ] ); b[3]=$?
# For each condition 1 through 3 define the actions when
# the condition is True or False
func10(){
echo "hop"
}
func11(){ :;}
func20(){
echo "hop-hop"
}
func21(){
echo "hip-hop-hap"
}
func30(){
echo "hip-hop"
}
func31(){ :;}
#loop through the array and execute the functions
for i in $(seq 1 ${#b[*]}) ; do
func${i}${b[i]}
done
Edits:
Replaced {(:)} by { :;} per Charles Duffy suggestion
The use of $(seq) in the loop is inefficient, yet explicitly
incrementing the loop counter seems a bit too much to type.

Difference between chaining `test` and using an `if` statement

Is there a difference between:
[ -f $FOO ] && do_something
...and:
if [ -f $FOO ]; then
do_something
fi
I thought they were equivalent, as [ is just an alias to test, which exits 0 when the condition passes. However, in a script I have written, I'm checking whether some environment variables are set and, if they are not, to bail out. In which case:
[ -z "${MY_ENV1+x}" -a -z "${MY_ENV2+x}" ] && fail "Oh no!"
...always seems to bail out; even when $MY_ENV1 and $MY_ENV2 are set. However, the if version works correctly:
if [ -z "${MY_ENV1+x}" -a -z "${MY_ENV2+x}" ]; then
fail "Oh no!"
fi
...where fail is defined as:
fail() {
>&2 echo "$#"
exit 1
}
I cannot diagnose the source of your problem but answering the question in your title, this is the only difference between FIRST && SECOND and if FIRST; then SECOND; fi that I know.
If FIRST evaluates to true, there is no difference.
If FIRST evaluates to false, then the result of the entire expression FIRST && SECOND is false. If your shell is has the -e flag set, this will cause it to abort. The version using the if statement, on the other hand, will never produce any result if FIRST evaluates to false so the shell will continue happily even if -e is set.
Therefore, I sometimes write the more verbose if FIRST; then SECOND; fi if I cannot afford a failure status. This can be important, for example, when writing Makefiles or if you make it a general habit to run your scripts with -e.

Is there no unless in shell script?

I tried to do the opposite of if in shell to only do something if a value in a hash doesn't exist. I've learned to create a hash in bash from here:Associative arrays in Shell scripts
declare -A myhash
I declared a simple hash:
myhash[$key]="1"
and
[ ${myhash[$key]+abc} ]
to see if myhash[$key] exist or not from here: Easiest way to check for an index or a key in an array?
I also learned to add ! in front of an expression to do the opposite in if clause from here:How to make "if not true condition"?
But the following expression
if ! [ ${myhash[$key]+abc} ]; then
echo $key
fi
doesn't work and no information is printed. There is no error message.
I tried this:
[ ${myhash[$key]+abc} ] && echo $key
And got the error message:
abc: command not found
Can anyone tell me how to make it work?
First, this
[ ${myhash[$key]+abc} ]
will test if the result of the parameter expansion is empty or not. Either ${myhash[$key]} exists and expands to a string (which itself may or may not be empty), or it does not and it expands to the string "abc". Assuming you don't set myhash[$key]="", the expansion always produces a non-empty string, and so the command succeeds. As a result,
! [ ${myhash[$key]+abc} ] would always fail.
Assuming "abc" is not a valid entry in myhash, you need to actually check if the parameter expands to an actual value, or "abc" for unset keys:
if [[ ${myhash[$key]+abc} == abc ]]; then
echo "$key does not exist in myhash"
else
echo "myhash[$key]=${myhash[$key]}"
fi
The reason nothing is printed is because you check if the value is unset after setting it.
Here's what you're doing:
#!/bin/bash
key=foo
declare -A myhash # create associative array
myhash[$key]="1" # set the value
if ! [ ${myhash[$key]+abc} ]; then # check if value is not set
echo $key # if so, print key
fi
And now you're asking "Why isn't the unset check triggering when the value is set?"
If you instead wanted to check if the key is set, you should not have inverted the existence check using !.
As for [ ${myhash[$key]+abc} ] && echo $key giving abc: command not found, I can't explain that. It would never normally happen.
That is what you would have seen if you did ${myhash[$key]+abc} && echo $key though. Are you sure you're correctly presenting the actual examples you ran, and the actual results you got
Easiest way to check for an index or a key in an array? shows how to check if it exists with [ ${array[key]+abc} ].
If it does, the command exits successfully, so the portion after the && gets executed (echo "exists")
You want to check if it doesn't exist. So you want your command to be executed if the [..] portion exits unsuccessfully. So use || instead of &&:
[ ${myhash[$key]+abc} ] || echo "does not exist"
&&: carry on with next command only if first succeeded
||: only carry on with next command if first did not succeed

Bash "if" statement throws "too many arguments"

This is the code that produces the error:
#!/bin/bash
types=([i]=Info [w]=Warning [e]=Error [se]=Severe)
function isValidType
{
for type in "${!types[#]}"
do
if [ $1 == $type ]; then
return 0
fi
done
return 1
}
if [ isValidType "$msgType" -eq 1 ]; then # <---- error here
echo "Invalid type."
exit 0
fi
The syntax of an if statement is:
if <list>; then <list>; [elif <list>; then <list>;]* [else <list>;]? fi
where <list> is any sequence of "pipelines" separated by ;, &&, or ||. (A pipeline is one or more simple commands separated by | symbols.)
The if statement is evaluated by first executing the <list> following the if, and checking the return code, which will be the return code of the last simple command executed. Based on that, a decision is made as to whether to execute the <list> following then (if the first list succeeeded) or to proceed with the elif tests and/or else clause.
Nowhere in that syntax does a [ appear, and for a good reason. [ is actually a command. In fact, it is almost a synonym for test; the difference is that [ insists that its last argument be ].
It's sometimes convenient to use [ (although it is almost always more convenient to use [[, but that's an essay for another day), but it is in no way obligatory. If you just want to test whether a command succeeded or not, then do so:
if isValidType "$msgType"; then
# it's valid
else
# it's not valid
fi
If you only need to do something if it didn't work, use the ! special form:
if ! isValidType "$msgType"; then
# it's not valid
fi
Change this
if [ isValidType "$msgType" -eq 1 ]; then
to
isValidType "$msgType"
if [ $? -eq 1 ]; then
[ is the test command which accepts an expression and doesn't work as you designed (Comparing the return value of the function).
To check if a function returns true or not the proper way to do this is:
if [ !isValidType ]; then
// would throw a flag for any options passed that are invalid
// error output
fi
This works if isValidType will be either one or zero.
I think the problem is in the you are checking the array.
Hope this helps.

Operations on boolean variables

In this question it has been shown how to use neat boolean variables in bash. Is there a way of performing logic operations with such variables? E.g. how to get this:
var1=true
var2=false
# ...do something interesting...
if ! $var1 -a $var2; then <--- doesn't work correctly
echo "do sth"
fi
This does work:
if ! $var1 && $var2; then
echo "do sth"
fi
Maybe somebody can explain why -a and -o operators don't work and &&, ||, ! do?
Okay boys and girls, lesson time.
What's happening when you execute this line?
if true ; then echo 1 ; fi
What's happening here is that the if command is being executed. After that everything that happens is part of the if command.
What if does is it executes one or more commands (or rather, pipelines) and, if the return code from the last command executed was successful, it executes the commands after then until fi is reached. If the return code was not successful the then part is skipped and execution continues after fi.
if takes no switches, its behavior is not modifiable in anyway.
In the example above the command I told if to execute was true. true is not syntax or a keyword, it's just another command. Try executing it by itself:
true
It will print nothing, but it set its return code to 0 (aka "true"). You can more clearly see that it is a command by rewriting the above if statement like this:
if /bin/true ; then echo 1 ; fi
Which is entirely equivalent.
Always returning true from a test is not very useful. It is typical to use if in conjunction with the test command. test is sometimes symlinked to or otherwise known as [. On your system you probably have a /bin/[ program, but if you're using bash [ will be a builtin command. test is a more complex command than if and you can read all about it.
help [
man [
But for now let us say that test performs some tests according to the options you supply and returns with either a successful return code (0) or an unsuccessful one. This allows us to say
if [ 1 -lt 2 ] ; then echo one is less than two ; fi
But again, this is always true, so it's not very useful. It would be more useful if 1 and 2 were variables
read -p' Enter first number: ' first
read -p' Enter second number: ' second
echo first: $first
echo second: $second
if [ $first -lt $second ] ; then
echo $first is less than $second
fi
Now you can see that test is doing its job. Here we are passing test four arguments. The second argument is -lt which is a switch telling test that the first argument and third argument should be tested to see if the first argument is less than the third argument. The fourth argument does nothing but mark the end of the command; when calling test as [ the final argument must always be ].
Before the above if statement is executed the variables are evaluated. Suppose that I had entered 20 for first and 25 for second, after evaluation the script will look like this:
read -p' Enter first number: ' first
read -p' Enter second number: ' second
echo first: 20
echo second: 25
if [ 20 -lt 25 ] ; then
echo 20 is less than 25
fi
And now you can see that when test is executed it will be testing is 20 less than 25?, which is true, so if will execute the then statement.
Bringing it back to the question at hand: What's going on here?
var1=true
var2=false
if ! $var1 -a $var2 ; then
echo $var1 and $var2 are both true
fi
When the if command is evaluated it will become
if ! true -a false ; then
This is instructing if to execute true and passing the arguments -a false to the true command. Now, true doesn't take any switches or arguments, but it also will not produce an error if you supply them without need. This means that it will execute, return success and the -a false part will be ignored. The ! will reverse the success in to a failure and the then part will not be executed.
If you were to replace the above with a version calling test it would still not work as desired:
var1=true
var2=false
if ! [ $var1 -a $var2 ] ; then
echo $var1 and $var2 are both true
fi
Because the if line would be evaluated to
if ! [ true -a false ; ] then
And test would see true not as a boolean keyword, and not as a command, but as a string. Since a non-empty string is treated as "true" by test it will always return success to if, even if you had said
if ! [ false -a yourmom ] ; then
Since both are non-empty strings -a tests both as true, returns success which is reversed with ! and passed to if, which does not execute the then statement.
If you replace the test version with this version
if ! $var1 && $var2 ; then
Then it will be evaluated in to
if ! true && false ; then
And will be processed like this: if executes true which returns success; which is reversed by !; because the return code of the first command was failure the && statement short circuits and false never gets executed. Because the final command executed returned a failure, failure is passed back to if which does not execute the then clause.
I hope this is all clear.
It is perhaps worth pointing out that you can use constructs like this:
! false && true && echo 1
Which does not use if but still checks return codes, because that is what && and || are for.
There is kind of a black art to using test without making any mistakes. In general, when using bash, the newer [[ command should be used instead because it is more powerful and does away with lots of gotchas which must, for compatibility reasons, be kept in [.
Since the original poster did not supply a realistic example of what he's trying to accomplish it's hard to give any specific advice as to the best solution. Hopefully this has been sufficiently helpful that he can now figure out the correct thing to do.
You have mixed here two different syntaxes.
This will work:
if ! [ 1 -a 2 ]; then
echo "do sth"
fi
Note brackets around the expressions.
You need the test command ([ in newer syntax) to use these keys (-a, -o and so on).
But test does nut run commands itself.
If you want to check exit codes of commands you must not use test.

Resources