Most people use BASH scripting as a quick and dirty way of doing things, often nothing more than a listing of commands with some variable substitution. Some brave soles go further and add for loops and perhaps group some code into a function.
However, BASH can do so much more, it's possible to create structured, complex and beautiful BASH programs if you utilise some of BASH hidden powers as a programming language. Below, I give some of useful tips which should boost your BASH scripts onto the next level.
Arithmetics using floating point numbers
BASH supports floating point numbers internally, but you need
to pipe the output tobc
to display it properly,
i.e.:
$ echo 10 / 3 | bc -l
3.33333333333333333333
If you, like me, think two decimals looks nicer, you must add this string (yes, it looks a bit funny):
$ echo "scale=2;" 10 / 3 | bc -l
3.33
Getting the call trace
BASH gives you full control over the call stack inside your programs. Here is a method which prints the call trace:
function print_call_trace()
{
# skipping i=0 as this is print_call_trace itself
for ((i = 1; i < ${#FUNCNAME[@]}; i++)); do
echo -n ${BASH_SOURCE[$i]}:${BASH_LINENO[$i-1]}:${FUNCNAME[$i]}"(): "
sed -n "${BASH_LINENO[$i-1]}p" $0
done
}
Now, if we use it to track the call stack in this wee program were two methods call eachother:
function one()
{
two "with some argument";
}
function two()
{
print_call_trace "something went wrong"
}
one "a nice start paramater=" /tmp
We get the following call stack:
myscript.sh:17:two(): print_call_trace "something went wrong"
myscript.sh:12:one(): two "with some argument";
myscript.sh:20:main(): one "a nice start paramater=" /tmp
This makes debugging your hundreds of lines of BASH scripts a doodle, right? ;-)
Getting auto completion and source code navigation
If you're an Emacs user, I cannot recommend enough that you use auto-complete-mode for completion of member variables and method names and etags for navigating your source code. Using etags, there's no problem navigating a BASH project consisting of hundreds of BASH in many different directories.
Variable assignment with default values
BASH is extremely flexible with regards to assigning value to a variable if a given variable is set, letting you fall back to a default value if not. Where BASH really rocks, though, is that it allows you to have a default/fallback value on the default/fallback value!
Consider:
apple=apple orange=orange fruit=${apple-${orange}}
=> apple
Now, if we comment out the apple variable so that it's not set, we instead get:
apple=apple
orange=orange
fruit=${apple-${orange}}
=>
orange
Now, let's put a fallback value on the fallback value:
apple=apple
orange=orange
banana=banana
fruit=${apple-${orange-${banana}}}
=>
banana
Pretty cool, eh?
Debugging
If find for the most part, that running bash with the
-x
switch is the best way of debugging a script:
$ bash -x my-bash-script
How to grep from standard error
Sometimes, you want to operate on the messages sent to standard error (for instance grep for a certain error code). To do this, you must re-map the standard out and standard error:
$ command-with-error 2>&1 | grep "Error code"
One use case for this, is if you want to grep the output from
thejava -version
command. For some reason, this
non error output is printed to standard error instead of
standard out (!):
$ java -version 2>&1 | grep version
java version "1.6.0_31"
return true;
There, I hope I've managed to encourage you to approach BASH programming with fresh eyes and a new perspective. The Advanced Bash-Scripting Guide is a primer on the subject and a good choice for further reading. I've also found great joy in reading Steve Bourne's classic from 1978 as well as Chris F. A. Johnson's blog
Happy BASHing!