= Shell Script Guidelines = == Software == We write scripts for {{{bash}}} and {{{bash}}} only. == Script Preamble == Any shell script should always start with the following preamble {{{ #!/bin/bash # $Id$ # # Author: # Date: # # Short purpose description. # # USAGE: # Detailed usage information goes here. Behavior and command line # switches etc # }}} == doms_env.sh == The DOMS project includes a file {{{bin/doms_env.sh}}} defining basic functions and varibles useful for scripts. A short overview: * {{{log_info}}} log a message with timestamp and hostname * {{{log_error}}} log an error message with timestamp and hostname * {{{log_fatal}}} log a fatal message with timestamp and hostname to both {{{stdout}}} and {{{stderr}}} then exit the script * {{{fail_on_error}}} throw a fatal error if the last command exited with exit code {{{!=0}}}. * {{{JAVA_HOME}}} automagically detects and sets {{{JAVA_HOME}}} if it is unset * {{{DATE_FORMAT}}} a date format string useful for time stamps (use {{{date +"$DATE_FORMAT"}}}) To include {{{doms_env.sh}}} in your scripts simply source it in: {{{ source doms_env.sh }}} To distribute it in your packages simply include a copy in your setup. This is not an ideal solution, but is acceptable since the script is really simple and not likely to receive many updates == Code Style == ''There is no excuse for ugly code just because it happens to be a shell script!'' ==== Indentation ==== We use 4 white spaces for indentation. Comment strings have one space after the {{{#}}} like in the preamble above. ==== Function Documentation ==== Functions are documented like so {{{ # # This function is really cool. # You should try it! # function brew_coffee () { # FIXME: Implement me } }}} With extra comment lines in top an bottom of the documentation. ==== Literals and Variable Naming ==== Avoid hardcoded literals where ever it makes sense (as you would in Java code too). Variables should have short descriptive to-the-point names. ''Do not'' {{{ echo "Hello world" > my_file.txt scp my_file.txt mikkel@pc134.sb:/~ }}} ''but do'' {{{ MY_FILE="my_file.txt" REMOTE="mikkel@pc134.sb:/~" echo "Hello world" > $MY_FILE scp $MY_FILE $REMOTE }}} ==== Inline Invocations ==== We use {{{$(command)}}} instead of backpings {{{`}}} for running inlined commands. For example {{{ FILE=$HOME/my_file.xml NAME=$(basename $FILE .xml) }}} ==== Workflow ==== Factor you control flow into well-named and documented functions and invoke them from a {{{Workflow}}} section in the bottom of the script. This make it easy to change the workflow or do partial runs by commenting out lines. Fx: {{{ # # Roll a tarball containing all sensitive files and store the # absolute file path in $BACKUP_FILE # function create_backup () { # code goes here } # # Upload $BACKUP_FILE to $BACKUP_SERVER # function upload_backup () { # code goes here } # # Workflow # create_backup upload_backup }}} ==== Fail on Errors ==== Exit gracefully with a message on {{{stderr}}} if an error occur. You should check for errors after any command that might cause errors. Fx {{{ scp $FOO $BAR if [ "$?" != "0" ]; then echo "Failed to copy $FOO to $BAR. Bailing out." 1>&2 exit 1 fi; }}} or by using {{{doms_env.sh}}}: {{{ scp $FOO $BAR fail_on_error "Failed to copy $FOO to $BAR. scp exited with $?" }}} ==== Working Directory ==== Scripts should be ''working-directory-independent'', meaning that they do the same thing no matter what working directory they are invoked from. Ie. both these invocations should do the same: {{{ mikkel@pc134:~/Applications/Summa$ start-server.sh & mikkel@pc134:~/$ Applications/Summa/start-server.sh & }}} If you need to change the working directory to that of the script file do it like this {{{ DEPLOY=$(dirname $0) pushd $DEPLOY > /dev/null # # Main script body # popd > /dev/null }}} == Never Background == You scripts should never background unless explicitly instructed to. Ie. don't end them with {{{ my_long_running_command & }}} == Clean Backgrounding == If you have run something in the background run it with {{{setsid}}}, like this {{{ setsid long_running_command > long_command.log }}} This will run the command in a different session and ensure that it does not exit even if the parent session crashes. == Hints == IDEA is horrible for editing shell scripts. GEdit bundled with Gnome (also just called "Text Editor") has nice syntax highlighting for shell scripts.