Writing clearer Bash scripts

Slashdot it! Delicious Share on Facebook Tweet! Digg!

Nicely Embedded

Functions take names in the same way as variables. Take care not to select names that are system functions or commands – especially avoid test. Developers are forever arguing over where to put the curly brackets that enclose functions. Google suggests putting the opening curly bracket at the end of the function name line and the end bracket on a separate line at the end:

function my_function() {
<... commands ...>

In this example, the keyword function helps quickly identify the statement as such. However, this might complicate porting, so most style guides advise against using the function keyword. Identify local variables inside a function with local . This step helps avoid flooding the global namespace or possibly changing the wrong variable elsewhere:

calculate() {
  local amount=1

Get into the habit of checking the return value of a function. If a function returns nothing, you should at least confirm a successful end or an error. You must test all passed parameters. False or omitted parameters from users can lead to undesired results. If arguments are passed with $@ or $* , Google recommends always giving $@ precedence. Whereas $* consolidates all arguments into one with a string value, $@ leaves the arguments as they are [3].

Collect all function definitions at the beginning of the script, ideally just after the constants. To quickly find the beginning of a long script, wrap the main program in a function called main. Google's style guide recommends this for all scripts with at least one function defined. The very end of the script would then have the statement main "$@".

Going Outside

Listing 4 uses so-called backticks (left-facing single quotes) to embed a command. This syntax is error prone and confusing, especially with embedded commands. Thus, you should replace the backticks with the more current notation $(<command>) . In Listing 4, the corresponding line would be replaced with the following:

for i in $(seq 1 ${NUMBER_IMAGES}); do

Always use full path names when processing a file, or your files might end up in the wrong directories. Using rm and other means to delete files also runs the risk of destroying needed directories.

Google gives as examples the dreaded rm -v * , which deletes all subdirectories with it, and rm -v ./* , which deletes all files on the hard drive. Also stay away from ls and its output. The returned filenames might contain line breaks, and different systems have different output conventions for this command. Also stay away from eval , which might give you loss of control over the code that the shell executes. Additionally, you can't tell whether the command was successful or how to change it if an error occurred.

Give the built-in Bash commands precedence and don't concatenate strings, for example, with external programs. For one thing, you don't know if the program even exists on other computers. For another, updates to the program can modify its behavior. Because Bash doesn't need to start a new process, running the script with the internal commands is much faster – especially if loops come into play.

If you're calling external programs in the script, use options in their long form, such as grep --version instead of grep -V . In this version, the meaning of the option becomes clearer in the longer form. If your script provides options, they should be available in long and short forms. Suggestions for developing parameters are included in the Free Software Foundation's GNU Coding Standards [5], and the most commonly used options are described at the Linux Documentation Project [6].

Buy this article as PDF

Express-Checkout as PDF

Pages: 4

Price $0.99
(incl. VAT)

Buy Ubuntu User

Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content