Useful Bash functions (HPR Show 1757)

Dave Morriss

Useful Bash functions (HPR Show 1757)

Overview

I enjoy writing Bash scripts to solve various problems. In particular I have a number of scripts I use to manage the process of preparing a show for HPR, which I am developing at the moment.

My more complex Bash scripts use a lot of functions to perform the various tasks, and, in the nature of things, some of these functions can be of use in other scripts and are shared between them.

I thought I would share some of these functions with HPR listeners in the hopes that they might be useful. It would also be interesting to receive feedback on these functions and would be great if other Bash users contributed ideas of their own.

Example Functions

The following functions are designed to be used in shell scripts. I have a few other functions, some of which I use from the command line, but I will leave discussing them for another time.

The way I usually include functions in scripts is to keep them all in a file I call function_lib.sh in the project directory. I then add the following to my script (the variable BASEDIR holds the project directory):

#
# Load library functions
#
LIB="$BASEDIR/function_lib.sh"
[ -e $LIB ] || { echo "Unable to load functions"; exit; }
source $LIB

The pad function

This is a simple function whose purpose is to write formatted lines to the screen. It outputs some text, padded to a chosen length using a chosen character. It adds the padding on the right, left or both sides to make the text centred in the width.

The arguments it requires are:

The text to display

The desired length of the padded string (default 80)

The character to pad with (default '-')

The side the padding is to be added: L, R or C (centre) (default R)

The function might be called as follows to achieve the results shown:

pad 'Title ' 40 '='
Title ==================================

pad ' Title' 40 '=' L
================================== Title

pad ' Title ' 40 '=' C
================ Title =================

It can also be used to output a line of 80 hyphens with the call:

pad '-'

I often use this function to generate lines and headers in reports I display on the terminal.

Code

  1  #===  FUNCTION  ================================================================
  2  #         NAME: pad
  3  #  DESCRIPTION: Pad $text on the $side with $char characters to length $length
  4  #   PARAMETERS: 1 - the text string to pad (no default)
  5  #               2 - how long the padded string is to be (default 80)
  6  #               3 - the character to pad with (default '-')
  7  #               4 - the side to pad on, L or R  or C for centre (default R)
  8  #      RETURNS: Nothing
  9  #===============================================================================
 10  pad () {
 11      local text=${1?Usage: pad text [length] [character] [L|R|C]}
 12      local length=${2:-80}
 13      local char=${3:--}
 14      local side=${4:-R}
 15      local line l2
 16
 17      [ ${#text} -ge $length ] && { echo "$text"; return; }
 18
 19      char=${char:0:1}
 20      side=${side^^}
 21
 22      printf -v line "%*s" $(($length - ${#text})) ' '
 23      line=${line// /$char}
 24
 25      if [[ $side == "R" ]]; then
 26          echo "${text}${line}"
 27      elif [[ $side == "L" ]]; then
 28          echo "${line}${text}"
 29      elif [[ $side == "C" ]]; then
 30          l2=$((${#line}/2))
 31          echo "${line:0:$l2}${text}${line:$l2}"
 32      fi
 33  }

Explanation

I use a Vim plugin called Bash Support which can generate a standard comment template and function boilerplate, and I have used this here to generate this function and the comment template in lines 1-9.

The function starts (lines 11-14) by declaring a number of local variables to hold the arguments. Only one argument, the text to output, is mandatory. The declaration of variable text uses the Bash parameter manipulation feature Display Error if Null or Unset which will abort the function and the calling script with an error message if no value is provided.

The other variable declarations supply default values using the Bash feature Use default values.

One of the instances where the function needs to take special action is if the supplied text is as long or longer than the total length. The expression on line 17 tests for this, and if found to be true it simply displays the text and returns from the function.

Next (lines 19 and 20) the variable char is processed, ensuring it's only one character long, and side is forced to upper-case.

The next part (line 22) uses printf to create the padding characters. The -v option to printf writes the result to a variable. The format string just consists of a %s specifier, used for writing a string. The asterisk (*) after the percent sign (%) causes printf to get the width of the string from the argument list.

The first argument to printf (after the format string) is the result of an arithmetic expression where the length of the text is subtracted from the desired length. The second argument is a space. So, the printf generates a space-filled string of the required length and stores it in the variable line.

The next statement (line 23) replaces all the spaces with the padding character. This uses the Bash feature Pattern substitution.

Finally, the function uses an if statement (lines 25-32) to determine how to display the text and the padding. If side is R or L the padding is on the right or left respectively. If it is C then half of the padding is placed on one side and half on the other. Parts of the padding string are selected with the Bash feature Substring Expansion (line 31).

The function does a good enough job for my needs. It does not deal with the case where the padding character is a space, but that is not a problem as far as I am concerned. It may be a little too simplistic for your tastes.

The yes_no function

This another simple function which asks a question and waits for a yes/no reply. It returns a true/false result so it can be used thus:

if ! yes_no 'Do you want to continue? ' 'No'; then
    return
fi

It takes two arguments:

The prompt string

An optional default value.

It returns true (0) if the response is either Y or Yes, regardless of case, and false (1) otherwise.

Code

  1  #===  FUNCTION  ================================================================
  2  #         NAME: yes_no
  3  #  DESCRIPTION: Read a Yes or No response from STDIN and return a suitable
  4  #               numeric value
  5  #   PARAMETERS: 1 - Prompt string for the read
  6  #               2 - Default value (optional)
  7  #      RETURNS: 0 for a response of Y or YES, 1 otherwise
  8  #===============================================================================
  9  yes_no () {
 10      local prompt="${1:?Usage: yes_no prompt [default]}"
 11      local default="${2// /}"
 12      local ans res
 13
 14      if [[ -n $default ]]; then
 15          default="-i $default"
 16      fi
 17
 18      #
 19      # Read and handle CTRL-D (EOF)
 20      #
 21      read -e $default -p "$prompt" ans
 22      res="$?"
 23      if [[ $res -ne 0 ]]; then
 24          echo "Read aborted"
 25          return 1
 26      fi
 27
 28      ans=${ans^^}
 29      ans=${ans//[^YESNO]/}
 30      if [[ $ans =~ ^Y(E|ES)?$ ]]; then
 31          return 0
 32      else
 33          return 1
 34      fi
 35  }

Explanation

The function starts (lines 10 and 11) by declaring a number of local variables to hold the arguments. Only one argument, the prompt, is mandatory. The declaration of variable prompt uses the Bash parameter manipulation feature Display Error if Null or Unset which will abort the function and the calling script with an error message if no value is provided.

The declaration of the default variable (line 11) copies the second argument and strips any spaces from it. This is because the answers catered for must not contain spaces.

If the default variable is not empty (lines 14-16) the string "-i" is prepended to it. This is going to be used as an option in the following read command. Note that the substitution of default should really be enclosed in double quotes if it contained spaces, but since we have stripped them out previously we can do this.

Next (line 21) a read command is issued to obtain input from the user.

The "-e" option ensures that the readline library is used to read the value. This permits line editing in the same way as on the command line.

If there is a default value then this is passed through the "-i" option which we have already added to the default variable. If there is no default value then nothing will be substituted here.

The "-p" option specifies the prompt string.

The result of the read is written to the variable ans.

The read command returns a true or false result (as do all Bash commands), and this can be found in the special variable "?". This is stored in the local variable res (line 22).

If the res variable is not true (0) then the following if statement (lines 23-26) will display "Read aborted" and exit the function with a false result. This false result from the read will result from the user pressing CTRL-D (meaning end of file) to abort the script.

The variable ans contains the answer the user typed (or accepted) and this is then processed in various ways (lines 28 and 29). First it is forced to upper case, then any letters other than "YESNO" are removed.

Finally, an if statement (lines 30-34) compares ans to the regular expression ^Y(E|ES)?$. This matches if the answer begins with a Y and is optionally followed by an E or by ES. If there is a match the function returns true (0), otherwise it returns false (1).

This way of doing things means that the reply 'Yup great' is stripped down to YE which is a match. Many other words that reduce to Y, YE or YES like 'Yeast' also match. This might not be a good idea in your particular case.

The other aspect of this function you might find slightly undesirable is the way the default is provided. If given, the default value will be on the input line and to override it you will need to delete it (CTRL-W is what I use). I am happy with this but you might not be!

Links

Bash Support Vim plugin: http://www.vim.org/scripts/script.php?script_id=365

HPR episode Bash parameter manipulation: http://hackerpublicradio.org/eps.php?id=1648

How to write functions (from The Linux Documentation Project):

Functions: http://tldp.org/LDP/abs/html/functions.html

Complex Functions and Function Complexities: http://tldp.org/LDP/abs/html/complexfunct.html

Examples of functions in scripts: http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_11_02.html

Download the pad and yes_no functions: hpr1757_functions.sh