Bash Style Guide and Coding Standard

Basic structure
The basic structure of a script simply reads:



The shebang
If possible (I know it's not always possible!), use a shebang. The shebang serves two purposes:
 * it specifies the interpreter when the script file is called directly: If you code for Bash, specify bash!
 * it documents the desired interpreter (so: use bash when you write a Bash-script, use sh when you write a general Bourne/POSIX script)

Variables
Variable names must be meaningful and self-documenting. Long names are structured by underscores to improve legibility. If a name is not self-documenting, the meaning and use must be described when it first occurs by a comment. The must be declared and initialized. [ -e "$1" ] && expand --tabs=$number "$1" > "$1.expand"

This line checks whether the file exists whose name has been handed over in parameter $1. Since the evaluation of logic terms is aborted, as soon as the result has been determined (the “short circuit evaluation”) further processing is omitted if the prior condition is false. The return value of the last command is stored in the variable $? and can be used for further process control: mkdir "$new_directory" if [ $? -ne 0 ] then ... fi 2> /dev/null If it has not been possible to create a directory in this example, the return value of mkdir is not equal to zero. The variable $? is used also to check the return value of a function.

Global Variables
Global variables are to be declared with short names and commented directly at the top of the script. That makes them easy to find. They have to be lowercase and start with 'g' (global).

Constants
Principally, the following applies for all programming languages: No constants must be included in the program text ! In particular numeral constants do not have another immediate meaning apart from their value. The meaning of the value will only become clear in the specific text context. In case of value changes of multiple occurring constants an automatic replacement in the editor is not possible, because the value might have been used in different meanings. Such program texts therefore are difficult to maintain. For the handling of constants - and of course also constant texts (such as file names) - the following recommendations apply:

Long continuous texts (e.g. descriptions, help for invocation options) can be described as here documents. cat <<- EOT List and/or delete all stale links in directory trees. usage : $0 [-d] [-oD logfile] [-l] [-h] [starting directories] -d delete stale links -l list stale links (default) -o write stale links found into logfile -D delete stale links listed in logfile -h display this message EOT

Function definitions
Unless the code has reasons to not do, all needed function definitions should be declared before the main script code is run. This gives a far better overview and ensures that all function names are known before they are used. Since a function isn't parsed before it is executed, you usually don't even have to ensure a specific order. The portable form of the function definition should be used, without the function keyword (here using the grouping compound command): getargs { ... }

Length of line
The total length of a line is less than 81 characters. By means of carriage return (character \ at line end) the generation of an overlong line is avoided. The table orientation increases legibility.

Indentation
Indentation is made by 4 spaces. The indentation of program constructions has to agree with the logic nesting depth. The indentation

Introductory comments in files
Every file must be documented with an introductory comment that provides information on the file name and its contents:
 * 1) !/bin/bash
 * 2) FILE: filename.sh
 * 3) USAGE: filename.sh [-a] [-bc something] anotherthing
 * 4) DESCRIPTION: Does do-ey things.
 * 5) OPTIONS: see function ’usage’ below
 * 6) REQUIREMENTS: ---
 * 7) BUGS: ---
 * 8) NOTES: ---
 * 9) AUTHOR: firstname lastname, email address
 * 10) COMPANY: ---
 * 11) VERSION: ---
 * 12) CREATED: YYYY-MM-DD
 * 13) REVISION: YYYY-MM-DD
 * 1) BUGS: ---
 * 2) NOTES: ---
 * 3) AUTHOR: firstname lastname, email address
 * 4) COMPANY: ---
 * 5) VERSION: ---
 * 6) CREATED: YYYY-MM-DD
 * 7) REVISION: YYYY-MM-DD
 * 1) COMPANY: ---
 * 2) VERSION: ---
 * 3) CREATED: YYYY-MM-DD
 * 4) REVISION: YYYY-MM-DD
 * 1) CREATED: YYYY-MM-DD
 * 2) REVISION: YYYY-MM-DD
 * 1) REVISION: YYYY-MM-DD

Comments
Do it. Start comments with a space. For the scope and style of the comments the following applies: Short, concise and sufficiently accurate. Comprehensive descriptions are a matter of external documentation. The structure or the trick used is described only in exceptional cases. Do not just put the obvious code into words.

Line end comments
If there are consecutive line end comments, make sure they start in the same column

Section comments
If there is a set of instructions is to be commented, indent the section comment according to the instructions nesting depth.
 * 1) comment on the next few instructions
 * 1) comment on the next few instructions

Function comments
Each function is described by an introductory comment. This comment contains the function name, a short description and the description of the parameters (if available). The name of the author and the date of issue should be added in case of subsequent amendments.
 * 1) FUNCTION FunctionName
 * 2) DESCRIPTION: Display usage information for this script.
 * 3) PARAMETER 1: ---
 * 1) DESCRIPTION: Display usage information for this script.
 * 2) PARAMETER 1: ---

Success verification
If there has to be a minimum number or a specified number of command line parameters, this number has to be verified. In case of an error, the script will be terminated with an error message and/or an indication of the required call parameters. The parameter values handed over have to be checked for validity as well. If, for example, the name of an entry file is handed over, it has to be checked before read access whether this file exists and can be read.

Execution and summary reports
Scripts which will be used interactively should display a summary report. It tells whether the script ran properly and it can be used to check the plausibility of the results, e.g. mn4:~/bin # ./stale-links -o stale-links.log /opt ... searching stale links ... 1. stale link: ’/opt/dir link 23’ 2. stale link: ’/opt/file link 71’ 3. stale link: ’/opt/file link 7’ stale links found : 3 stale links deleted : 0 logfile: ’stale-links.log’ Detailed execution reports are written into logfiles. They need also to be available to help diagnose failure.

Temporary files
Temporary files for storing of comprehensive intermediate results are usually generated in the central directory tmp and removed again after their use. For generation of accidental names mktemp can be used (see man 1 mktemp): function cleanup_temp { [ -e $tmpfile ] && rm --force $tmpfile exit 0 } trap cleanup_temp SIGHUP SIGINT SIGPIPE SIGTERM tmpfile=$(mktemp) || { echo "$0: creation of temporary file failed!"; exit 1; } rm --force $tmpfile The function cleanup_temp will be called if one of the specified signals is caught from the trap statement. This function then deletes the temporary file. The file survives a termination with SIGKILL because this signal can not be caught.
 * 1) Cleanup temporary file in case of keyboard interrupt or termination signal.
 * 1) Cleanup temporary file in case of keyboard interrupt or termination signal.
 * 1) ... use tmpfile ...

Backup copies
If several older copies of files have to be retained, the use of the date as a component of the file names of the copies is recommended: timestamp=$(date +"%Y%m%d-%M%S") mv logfile logfile.$timestamp The file logfile is now being renamed, e.g. in logfile.20041210-3116. The components of date and time are organized in reversed order. The files named following this convention are listed in directory lists sorted chronologically. Intermediate results Intermediate results that are also written into the files can be output by the use of tee as well as to the default output. In this way they can serve for process control or for testing of the script: echo $output_string | tee --append $TMPFILE
 * 1) generate timestamp : YYYYMMDD-mmss

Command line options
Invoking of external programs Invoking of external programs: If system programs are invoked, the long forms of command line options have to be used in a script, if these are available. The long names are mostly self-documenting and thus simplify reading and understanding a script. In the following useradd-instruction the long forms of the commands -c, -p and -m are used: useradd --comment --password --create-home $loginname $full_name $encrypted_password

Use of Shell Builtin Commands
If possible shell buitins should be preferred to external utilities. Each call of sed, awk, cut etc. generates a new process. Used in a loop this can extend the execution time considerably. In the following example the shell parameter expansion is used to get the base name and the directory of a path: for pathname in $(find $search -type f -name "*" -print) do basename=${pathname##*/} dirname=${pathname%/*} ... done
 * 1) replaces basename(1)
 * 1) replaces dirname(1)

Pattern matching in strings can be done with the comparison operator. metacharacter=’[~&|]’ if "$pathname" =~ $metacharacter then # treat metacharacter fi

SUID/SGID-Scripts
A shell script depends on user input, the process environment, the initialization files, the system utilities used etc. Shell languages are not well suited to write secure scripts because all of the above mentioned facilities (others as well) can be used to attack your system. Utilities may be vulnerable themselves. There are a number of precautions which should be taken to run a SUID/SGID script [4, 6]. Here the most important without the claim for completeness:
 * Execute the script from a directory where it can not be changed unauthorized.
 * Check if the environment variable is empty.
 * Set umask BASH_ENV to 077.
 * Reset the environment variables PATH and IFS to secure values.
 * Change to a safe working directory and validate this change.
 * Use absolute path names for system utilities and data files.
 * Check all return codes from system utilities.
 * Signify the end of the option list with --.
 * Quote all command line parameters (e.g. "$1").
 * Check the user input for shell metacharacters and other unwanted characters.
 * Check user supplied pathnames (absolute/relative).
 * Set the shell option noclobber to avoid overwriting existing files.
 * Create temporary files in a save directory. Use mktemp

Testing
package. It can interact also with the graphic debugger front end ddd4
 * bash -n
 * Test scope
 * Use of echo
 * Testing using bash options
 * -n noexec Commands are not executed, only syntax check (see 10.1)
 * -v verbose Outputs the lines of a script before execution.
 * -x xtrace Outputs the lines of a script after replacements.
 * Pseudo signal DEBUG and EXIT
 * The debugger bashdb3 works with bash from version 3.0 and can be installed simply from the source

Further sources of information
The most important source of information are the manuals for the actually installed version of the shell and the system utilities. Occasionally technical journals publish articles on shell programming. Besides that there are a number of textbooks on shell programming. For questions concerning system programming and security a good point to start is,. There are many platform oriented internet sites on security issues and new developments.