Twitter

Some bash tips -- 6 -- && and ||

This blog is part of a bash tips list I find useful to use on every script -- the whole list of it can be found here.

After having written hundreds of lines of shell and tons of IFs like this one:
if [[ -f /etc/passwd ]]; then
    echo "it exists !"
else
    echo "etc passwd not here !"
fi
you will naturally become more and more comfortable with the language and test more and more things in the command line before writting it in a script and then you'll start writting IFs like that in the command line:
$ if [[ -f /etc/passwd ]]; then echo "it exists !"; else echo "etc passwd not here !"; fi
it exists !
$
and you'll discover that you can also simplify the syntax and wrote it like that:
$ [[ -f /etc/passwd ]] && echo "it exists !" || echo "etc passwd not here !"
it exists !
$
Here you can see that && is basically the then and || is the else; we can verify this by just testing with a non existing file:
$ [[ -f /etc/passwd ]] && echo "it exists !" || echo "etc passwd not here !"
etc passwd not here !
$
Cool, right ? but && and || are note simply a then or an else;it is more that. Indeed, && is actually triggered when the result of the previous command is 0 (the command has succeeded) and || is triggered when the result of the previous command is not 0 (the command has failed) and if we look at the below tests and their return codes:
$ [[ -f /etc/passwd ]]; echo $?          
0     <== the file exists
$ [[ -f /etc/passwd2 ]]; echo $? 
1     <== the file does not exist
$
You can see that [[ -f /etc/passwd ]] returns 0 if it succeeds (the file exists) and not 0 if it fails (if the file does not exist) -- as any other Unix command. It also means that we can extend this && and || mechanism to other commands and make things easy.

Let's take as an example you want to add a parameter myparameter with a value if not already in a parameter file named parameters. We will use grep using the -q option (-q is for quiet, grep won't return any line, just the return code if it has find something (returns 0) or nothing has been found (returns not 0)):
$ grep -q myparameter parameters
$ echo $?
1
$
So now, how to add the parameter if it is not found into the file ? easy, using || !
$ cat parameters     <== we start with a empty parameters file
$ grep -q myparameter parameters || echo "myparameter=123" >> parameters
$ cat parameters     <== first execution, we add the parameter into the file
myparameter=123
$ grep -q myparameter parameters || echo "myparameter=123" >> parameters
$ cat parameters     <== next executions, the paramater is not added as it is already in the parameter file    
myparameter=123
$
Half a line of code in a script and you have your myparameter management and a simple for loop would do the same for many other parameters -- easy and super powerful, right ?

There're other things && and || help to make in a more robust way. Let's say you want to delete files in a directory, something like:
rm -f ${TMP}/*
The thing is that if you variable TMP is empty, you're gonna be in trouble (specially if your script runs as root :)) so I personally prefer to first move into the TMP directory and then delete the files, like:
cd "${TMP}"; rm -f *
But here again there is a caveat; indeed, it you cannot move into "${TMP}" (permission isue, wrong value of the TMP variable), the rm -f * will still be executed (unless you use set -e in your script which is very unlikely as almost none uses this option) but ... in the wrong place ! (and you'll be in trouble again). We can demonstrate this behavior with "pwd" instead of "rm" below:
# mkdir iexist
# pwd; cd iexist/; pwd
/root          <== pwd before cd
/root/iexist   <== pwd after the succesful cd
# cd ..
# pwd; cd idontexist/; pwd
/root          <== pwd before cd
-bash: cd: idontexist/: No such file or directory <== cd fails
/root          <== cd has failed but we are still at the original path and we execute a command (which could have been a rm in the wrong path !)
# set -e
# pwd; cd idontexist/; pwd
/root
-bash: cd: idontexist/: No such file or directory
$  <== with set -e we exit after the first error, nothing is executed after the failed cd
So here again, the easy and robust solution is to use && to execute your "rm" only if the "cd" succeeds; below an example with pwd instead of rm:
# cd iexist && pwd
/root/iexist     <== cd has succeeded, pwd is executed
# cd ..
# cd idontexist && pwd
-bash: cd: idontexist: No such file or directory <== cd has failed, pwd is NOT executed, this is the way to go !
#

&& and || is a first step on the road to becoming more of a oneliner but it also make your code more robust and this is definitely the way to go ! enjoy && and || !


< Previous bash tip / Next bash tip >

1 comment:

Some bash tips -- 18 -- paste

This blog is part of a shell tips list which are good to know -- the whole list can be found here. I really like finding a real usage for...