Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

  1. A "she-bang" line which can potentially be changed to invoke /bin/ksh if necessary
  2. A copyright notice
  3. Support for short (e.g. "-v") and long (e.g. "--verbose") options
  4. Support for extracting the value out of --long_arg=value options
  5. A basic structure for handling usage messages to be shown to the user when the flags/usage is not correct.


Code Block
languagebash
themeEclipse
#!/bin/bash
 
# Copyright 2020 ForgeRock AS. All Rights Reserved
#
# Use of this code requires a commercial software license with ForgeRock AS.
# or with one of its affiliates. All use shall be exclusively subject
# to such license between the licensee and ForgeRock AS.
#
# Comment description of what the script does here.
 
error=0
verbose=0
index=
basename=$(basename $0)

while getopts ':i:v-:' arg "$@"; do
  case $arg in
    -)  case "$OPTARG" in
           verbose)  verbose=1 ;;
           index*)   index=${OPTARG#*=} ;;
        esac ;;
    i)  index=$OPTARG ;;
    v)  verbose=1 ;;
    ?)  error=1 ;;
  esac
done

let x=OPTIND-1
shift $x

if (( error )); then
    echo "Usage: $basename [-v|--verbose] [-i index] [--index=ind] [arguments]"
    echo "-v"
    echo "--verbose    enable verbose debugging output"
    echo "-i ind"
    echo "--index=ind  use a value of \"ind\" for the index"
    echo "etc."
    echo "etc."
    exit 9
fi

...

Try to avoid the old, Bourne-style boolean operators which now have much better and more readable equivalents, i.e. replace

Code Block
languagebash
themeEclipse
if [ $# -gt 3 -a $v -le 5 ]; then ...

with the far more readable:

Code Block
languagebash
themeEclipse
if (( $# > 3 && v <= 5 )); then

Don't forget to use the Bash/Korn Shell style double square brackets for ALL string/file based tests.  These fix a number of bugs in the original Bourne Shell equivalent

Code Block
languagebash
themeEclipse
VAR=
if [ -f $VAR ]; then
    echo "This is a bug in the Bourne Shell where an empty variable appears to be a valid filename"
fi
Code Block
languagebash
themeEclipse
VAR=
if [[ -f $VAR ]]; then
    echo "You will never see this, because the bug is fixed in ksh and bash"
fi

...

Also avoid the use of the old Bourne Shell style backward facing quotes, i.e. replace

Code Block
languagebash
themeEclipse
ps -t `basename \`tty\``

with the more readable

Code Block
languagebash
themeEclipse
ps -t $(basename $(tty))

Chop values, as opposed to using sed

Do consider using the (admittedly rather arcane) "value chopping" constructs to extract values you need.  While difficult to read, they give their results without invoking external processes.  For example:

Code Block
languagebash
themeEclipse
# filenames are traditionally extracted from paths using "basename"
name=$(basename $0)
echo "Full path of script: $0"
echo "file name of script: $name"

# although arcane, this approach can also be used and will be very slightly faster
name=${0##*/}
echo "file name of script: $name"

# directory names are traditionally extracted from paths using "dirname"
dir=$(dirname $0)
echo "Directory containing script: $dir"

# or alternatively like this
dir=${0%/*}
echo "Directory containing script: $dir"

# the file suffix can be extracted as follows, although annoyingly you don't get an empty string if there is no file suffix
suffix=${name##*.}
echo "suffix of script: $suffix"

# With this, suffix will be empty if there is no suffix.  NOTE: Extract the suffix from the file name,
# as opposed to the full path because of the danger that one of the directories in the path contains a dot.
suffix=
[[ $name == *.* ]] && suffix=${name##*.}
echo "suffix of script: $suffix"

...

I also find the following functions useful.  Please feel free to add ones you find indispensable too:

Code Block
languagebash
themeEclipse
####################################################################################
# Print a message to the standard error and die.
#   parameter 1: exit status (default 255)
#   parmeters 2 and beyond: message to print to stderr
####################################################################################
function die() {
    local status=${1-255}; shift
    if (( $# > 0 )); then
        echo "$@" >&2
    else
        echo "die invoked, exiting"
    fi
    exit $status
}

####################################################################################
# Pass a variable name to this function and it will verify that the variable contains
# a value which represents a readable and searchable directory.  If it does not, kill
# the entire script.
#   parameter 1: variable name
####################################################################################
function verifyDirectory() {
    local param=$1
    local value=${!param}

    if [[ -z $value ]]; then
        die 48 "The variable $param is not set!"
    fi
    if [[ ! -d $value ]]; then
        die 49 "The variable $param is set to $value which is not a valid directory"
    fi
    if [[ ! -r $value ]]; then
        die 50 "The variable $param is set to $value which is non readable directory"
    fi
    if [[ ! -x $value ]]; then
        die 51 "The variable $param is set to $value which is a non searchable directory"
    fi
}

####################################################################################
# Pass a variable name to this function and it will verify that the variable contains
# a value which represents a readable file.  If it does not, kill the entire script.
#   parameter 1: variable name
####################################################################################
function verifyFile() {
    local param=$1
    local value=${!param}

    if [[ -z $value ]]; then
        die 52 "The variable $param is not set!"
    fi
    if [[ ! -f $value ]]; then
        die 53 "The variable $param is set to $value which is not a valid file"
    fi
    if [[ ! -r $value ]]; then
        die 54 "The variable $param is set to $value which is not a readable file"
    fi
}

####################################################################################
# Pass a variable name to this function and it will verify that the variable contains
# a value which represents a file with read/write permission.  If it does not, kill
# the entire script.
#   parameter 1: variable name
####################################################################################
function verifyWriteableFile() {
    local param=$1
    local value=${!param}

    if [[ -z $value ]]; then
        die 52 "The variable $param is not set!"
    fi
    if [[ ! -f $value ]]; then
        die 53 "The variable $param is set to $value which is not a valid file name"
    fi
    if [[ ! -r $value ]]; then
        die 54 "The variable $param is set to $value which is not readable"
    fi
    if [[ ! -w $value ]]; then
        die 54 "The variable $param is set to $value which is not writeable"
    fi
}

####################################################################################
# edit a property in the specified property file giving it the specified value
#   parameter 1: property name - which must already exist in the file
#   parameter 2: property value
#   parameter 3: property file name - which must exist and be writeable
####################################################################################
function set_property() {
    local property=$1
    local value=$2
    local target=$3
    local target_dir=$(dirname $target)

    verifyWriteableFile target
    local line=$(sed -n -e "/^$property *=/=" $target)
    if [[ -z "$line" ]]; then
        echo "Cannot find the property $property in the property file $target"
        return 1
    fi
    
    sed -i"" -e "$line c\\
$property = $value" $target
    
    return 0
}

An example usage of these would be as follows:

Code Block
languagebash
themeEclipse
HTTPD_PROPERTIES=/usr/local/apache2/conf/httpd.conf; verifyWriteableFile HTTPD_PROPERTIES

set_property org.forgerock.agents.config.postdata.preserve.dir /tmp $HTTPD_PROPERTIES
set_property com.sun.identity.agents.config.debug.level all $HTTPD_PROPERTIES

...

Indirection is more recent feature and is not supported as well as it should be.  In fact, it may not be possible to support it fully without breaking backwards compatibility with the Bourne shell, implying that it may never actually work correctly.  Here is a simple example where things work:


Code Block
languagebash
themeEclipse
$ cat foobar

function simple_indirection_example() {
    local name=$1
    local value=${!name}
    echo "Function $FUNCNAME invoked with $name, having value $value"
}
for arg; do
    simple_indirection_example $arg
done

$ ./foobar HOME
Function simple_indirection_example invoked with HOME, having value /Users/tonybamford

...

Things quickly fall apart when using the indirection syntax to access elements of an array.  Consider:

Code Block
themeEclipse
A=( one two three )
name=A

echo "Name of variable: $name"
echo "Default value: ${!name}"
echo "Second element: ${!name[1]}"
echo "Second element: ${${!name}[1]}"

...