Bash and shell scripting

Shellcheck

This utility (shell linter) is installed on PMSN. Use it without moderation. You can also use it online.

shellcheck myscript.sh

Debugging

  • Use “strict mode with pipefail” (exit without continuing at first error):

set -euo pipefail
  • Trace each command verbosely:

set -x

String replace

  • replace home with scratch from $SGE_O_WORKDIR, save into $SCRATCHDIR:

SCRATCHDIR=${SGE_O_WORKDIR/"home"/"scratch"}
  • name $OUTFILE and $LOGFILE, following $INFILE (change file extension):

INFILE=$1
TMPNAME=$(basename "${INFILE%.*}")
OUTFILE="$TMPNAME"-done.hdf5
LOGFILE="$TMPNAME".log
  • Explicit error message in case of unset variable

# ${TMPDIR:?"TMPDIR nonexistent"}

result=$(ls "${TMPDIR:?"TMPDIR nonexistent"}")
if [[ "$result" == "nonexistent" ]]
then
    echo "TMPDIR do not exist!"
    exit 1
fi

Pattern matching

#!/bin/bash

#shopt -s compat31

VARIABLE="Let's test this string!"

# This is for regular expressions:
# See https://www.shellcheck.net/wiki/SC2076
if [[ "$VARIABLE" =~ "Let's.*ring" ]]
then
    echo "#1 matched"
else
    echo "#1 nope"
fi

# without quotes
if [[ "$VARIABLE" =~ Let\'s.*ring ]]
then
    echo "#2 matched"
else
    echo "#2 nope"
fi

# And here we have Bash Patterns:
if [[ "$VARIABLE" == L*ing! ]]
then
    echo "#3 matched"
else
    echo "#3 nope"
fi

Results:

#1 nope
#2 matched
#3 matched

To get #1 matching, remove quotes around RegExpr, or set compat31 : shopt -s compat31

#!/bin/sh

thisString="1 2 3 4 5"
searchString="1 2"
# if you single quote your input, you could do this
# searchString=$1

case $thisString in
    # match exact string
    "$searchString" ) echo yep, it matches exactly ;;

    # match start of string
    "$searchString"* ) echo yep, it matches at the start ;;

    # match end of string
    *"$searchString" ) echo yep, it matches at the end ;;

    # searchString can be anywhere in thisString
    *"$searchString"* ) echo yep, it matches in the middle somewhere ;;

    *) echo nope ;;
esac

Results:

yep, it matches at the start

Associatives arrays

We can name array entries (aka named index).

  • tips :

tab[wwsi]="whatever/wwsi"
tab[wwoz]="whatever/to/wwoz"
tab[fip]="http://audio.scdn.arkena.com/11016/fip-midfi128.mp3"
  • unroll an array:

    • brute force:

      for i in "${tab[@]}"; do echo "${i}"; done
      
    • tip (induce loop):

      printf "%s\n" "${tab[@]}"; done
      

Resulting in code simplification (and readability):

  • Array traversal, knowing the index (here $value):

    • before :

      tindex=$(for i in "${tab[@]}"; do echo "${i}"; done | grep "${value}")
      for ((i=0; i<${#tab[@]}; i++))
      do
          if [[ "$tindex" == "{tab[$i]}" ]]
          then
              data="${tab[$i]}"
          fi
      done
      
    • with induce loop:

      tindex=$(printf "%s\n" "${tab[@]}" | grep "${value}")
      data="${tab[$tindex]}"
      

Warning

Not all these examples have been tested against recent bash and/or shelcheck. DO IT!!