Shells

Scripting Bash

Bash - or Bourne Again SHell - is the standard shell on our high-performance clusters. We do not attempt to provide full documentation, here, in the wiki. Instead, we like to point to a few great resources:

Both are dated, but still good.

Our Courses

As our HPC introductory course and many others heavily rely on Bash, we provide introductory courses for Bash (dubbed “Bash-Crash”), too. These are intended to provide the background for the HPC introductory course (and any HPC computing) and are intended for those (employees and students in Rhineland Palatinate, alike) with an interest in HPC. You can enroll here.

Other Shells

Some projects suggest or impose to use a different shell. This is, of course, possible on our systems, too.

List of available shells1 and the shebang to use:

ShellCall for interactive session*Shebang
Bourne Again SHell (Bash)$ bash#!/bin/bash
C-Shell$ csh#!/bin/csh
Korn Shell$ ksh#!/bin/ksh
TENEX-C-Shell$ tcsh#!/bin/tcsh
Z-Shell$ zsh#!/bin/zsh
  • Some shells provide an -i flag, which can be supplied to enforce an interactive shell.
  • We only support Bash.
  • Some cluster features (e.g. the environment exported by SLURM) might not be available.

If you choose to use a different shell - in this example zsh – you can try this snippet (in bash):

if [[ $- == *i* ]]; then
  if [[ "$SLURM_CLUSTER_NAME" == "" ]]; then
    if [ -z ${SBASH+x} ]; then
        export SHELL=/bin/zsh
        exec /bin/zsh -l
    fi
  fi
fi

It activates the zsh, e.g. upon login, when placed in the .bashrc-file. Calling

export SBASH=1 && bash

will let you return to bash at any time.

Best Practices

This is a completely arbitrary list - and only applicable to Bash! We make no claim for it to be complete.
  1. Always comment your code:
    This is a recommended practice which is not only applied to shell scripting but all other kinds of programming. Writing comments in a script helps you or some else going through your script understand what the different parts of the script do.
    For starters, comments are defined using the # sign.

  2. Make a script exit when it fails:
    Sometimes bash may continue to execute a script even when a certain command fails, thus affecting the rest of the script (may eventually result in logical errors). Use the line below to exit a script when a command fails: bash #let script exit if a command fails set -o errexit OR set -e Add || true to commands that you allow to fail.

  3. Make a script exit when bash uses an undeclared variable
    This is frequently a cause for confusion: Applications crash, when hitting an exception, e.g. an undeclared variable. So does the shell, doesn’t it?
    Shells – and Bash – are different. This may be desired or not. Yet, it is not just a source of confusion. It is a cause for a number of tickets (read: help request for the HPC team), too. This is because variables can be undeclared unintentionally. Therefore use the following line to instruct bash to exit a script when it attempts to use an undeclared variable: bash #let script exit if an unsed variable is used set -o nounset OR set -u

  4. Make broken pipes fail
    Use set -o pipefail in scripts to catch command fails in e.g. in first_command | grep <pattern>. The exit status of the last command that threw a non-zero exit code is returned.

  5. Surround your variables with {}
    Otherwise bash will try to access the $ENVIRONMENT_app variable in expansions like /path/$ENVIRONMENT_app, whereas you probably intended /path/${ENVIRONMENT}_app.

  6. Surround your variables with quotes
    For example in bashif [ "${NAME}" == "Kevin" ] , because if $NAME isn’t declared, bash will throw a syntax error (also see nounset). Use :- if you want to test variables that could be undeclared. For instance: bashif [ "${NAME:-}" == "Kevin" ] will set $NAME to be empty if it’s not declared. You can also set it to noname like so bashif [ "${NAME:-noname}" == "Kevin" ]

  7. Use functions in Scripts
    Except for very small scripts (with a few lines of code), always remember to use functions to modularize your code and make scripts more readable and reusable.
    The syntax for writing functions is as follows:
function check_integrity(){
    command1;
    command2;
}

OR

check_integrity(){
    command1;
    command2;
}

For single line code, use termination characters after each command like this:

check_integrity(){ command1; command2; }
  1. Use $(command) instead of legacy command for Substitution
    Command substitution replaces a command with its output. Use $(command) instead of backquotes command for command substitution.
    This even allows nesting commands without losing your sanity:bashparent=$(basename $(dirname $PWD))

  2. Use read-only to declare static variables
    A static variable doesn’t change; its value can not be altered once it’s defined in a script:bashreadonly my_constant=42

There are lots of odds and ends and particularly for new users this can be overwhelming. Be sure to check out our introductory courses.

Integrity Checking of Shell Scripts

However good a writer may be – she will always make mistakes. Now you can always invoke a script with bash -n <script> to check the script is without errors. Yet, just the other day we installed “shellcheck” on the login-nodes. If you run shellcheck <options> <script> it will spot (most likely) a number of code smells, potential errors and real ones – and sort them according to their respective severity.

Footnotes


  1. In case one is missing or you would like to use a different one, let us know. ↩︎