Bash Nagios plugin

Today lets have a look at one way to construct a nagios plugin in bash. I would usually write these in perl, but sometimes that is not possible. This plugin is actually written to be executed using NRPE.

#!/bin/bash
# bash nagios plugin

###
# Variables
###
OK=0
WARNING=1
CRITICAL=2
UNKNOWN=-1
TO_RETURN=${OK}
TO_OUTPUT=''

# Print usage information and exit
print_usage(){
    echo -e "\n" \
    "usage: ./check_uptime -w 20 -c 30 \n" \
    "\n" \
    "-w <days>    warning value\n" \
    "-c <days>    critical value\n" \
    "-h           this help\n" \
    "\n" && exit 1
}

###
# Options
###

# Loop through $@ to find flags
while getopts ":hw:c:" FLAG; do
    case "${FLAG}" in
        w) # Warning value
            WARNING_VALUE="${OPTARG}" ;;
        c) # Critical value
            CRITICAL_VALUE="${OPTARG}" ;;
        h) # Print usage information
            HELP=1;;
        [:?]) # Print usage information
            print_usage;;
    esac
done

###
# Functions
###

log_date(){
    echo $(date +"%b %e %T")
}

error() {
    NOW=$(log_date)
    echo "${NOW}: ERROR: $1"
    exit 1
}

warning() {
    NOW=$(log_date)
    echo "${NOW}: WARNING: $1"
}

info() {
    NOW=$(log_date)
    echo "${NOW}: INFO: $1"
}

# Do something
get_cmd_output(){
    #generate output
    echo `uptime | sed 's/.*up \([0-9]*\) day.*/\1/'` || error "failed to run command"
}

###
# Program execution
###
[ "${HELP}" ] && print_usage

if [ ${WARNING_VALUE} ] && [ ${CRITICAL_VALUE} ]
then
    CMD_OUTPUT=$(get_cmd_output)
else
    print_usage
fi

if [ "${CMD_OUTPUT}" ] && [ ${CMD_OUTPUT} -gt ${CRITICAL_VALUE} ]
then
    TO_RETURN=${CRITICAL}
elif [ "${CMD_OUTPUT}" ] && [ ${CMD_OUTPUT} -gt ${WARNING_VALUE} ]
then
    TO_RETURN=${WARNING}
elif [ "${CMD_OUTPUT}" ] && [ ${CMD_OUTPUT} -gt 0 ]
then
    TO_RETURN=${OK}
else
    TO_RETURN=${UNKNOWN}
fi

if [ $TO_RETURN == ${CRITICAL} ]
then
    TO_OUTPUT="CRITICAL "
elif [ $TO_RETURN == ${WARNING} ]
then
    TO_OUTPUT="WARNING "
elif [ ${TO_RETURN} == ${OK} ]
then
    TO_OUTPUT="OK "
else
    TO_OUTPUT="UNKNOWN "
fi

TO_OUTPUT="${TO_OUTPUT}| uptime=${CMD_OUTPUT};$WARNING_VALUE;$CRITICAL_VALUE"

echo "$TO_OUTPUT";
exit $TO_RETURN;

Lets break it down…

OK=0
WARNING=1
CRITICAL=2
UNKNOWN=-1

We define some readable names for the return codes.

TO_RETURN=${OK}

Set the initial return value to OK.

# Do something
get_cmd_output(){
    #generate output
    echo `uptime | sed 's/.*up \([0-9]*\) day.*/\1/'` || error "failed to run command"
}

Function to obtain the value we want to check. In this case uptime.

if [ "${CMD_OUTPUT}" ] && [ ${CMD_OUTPUT} -gt ${CRITICAL_VALUE} ]
then
    TO_RETURN=${CRITICAL}
elif [ "${CMD_OUTPUT}" ] && [ ${CMD_OUTPUT} -gt ${WARNING_VALUE} ]
then
    TO_RETURN=${WARNING}
elif [ "${CMD_OUTPUT}" ] && [ ${CMD_OUTPUT} -gt 0 ]
then
    TO_RETURN=${OK}
else
    TO_RETURN=${UNKNOWN}
fi

Check the value of uptime against our warning and critical values.

if [ $TO_RETURN == ${CRITICAL} ]
then
    TO_OUTPUT="CRITICAL "
elif [ $TO_RETURN == ${WARNING} ]
then
    TO_OUTPUT="WARNING "
elif [ ${TO_RETURN} == ${OK} ]
then
    TO_OUTPUT="OK "
else
    TO_OUTPUT="UNKNOWN "
fi

Set the visible output of the plugin. This output is not used by nagios.

TO_OUTPUT="${TO_OUTPUT}| uptime=${CMD_OUTPUT};$WARNING_VALUE;$CRITICAL_VALUE"

Construct the output string according to the nagios plugin developer guidelines.

Stay tuned. The perl version will be out soon.

For more information see:
http://nagiosplug.sourceforge.net/developer-guidelines.html#AEN201

ssh-copy-id missing in OS X

Not sure if anyone else has noticed, but OS X is missing ssh-copy-id. This utility is included with the ssh client in most major linux distros. As it turns out, it is just a shell script.

#!/bin/sh

# Shell script to install your public key on a remote machine
# Takes the remote machine name as an argument.
# Obviously, the remote machine must accept password authentication,
# or one of the other keys in your ssh-agent, for this to work.

ID_FILE="${HOME}/.ssh/id_rsa.pub"

if [ "-i" = "$1" ]; then
  shift
  # check if we have 2 parameters left, if so the first is the new ID file
  if [ -n "$2" ]; then
    if expr "$1" : ".*\.pub" > /dev/null ; then
      ID_FILE="$1"
    else
      ID_FILE="$1.pub"
    fi
    shift         # and this should leave $1 as the target name
  fi
else
  if [ x$SSH_AUTH_SOCK != x ] && ssh-add -L >/dev/null 2>&1; then
    GET_ID="$GET_ID ssh-add -L"
  fi
fi

if [ -z "`eval $GET_ID`" ] && [ -r "${ID_FILE}" ] ; then
  GET_ID="cat ${ID_FILE}"
fi

if [ -z "`eval $GET_ID`" ]; then
  echo "$0: ERROR: No identities found" >&2
  exit 1
fi

if [ "$#" -lt 1 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
  echo "Usage: $0 [-i [identity_file]] [user@]machine" >&2
  exit 1
fi

{ eval "$GET_ID" ; } | ssh ${1%:} "umask 077; test -d .ssh || mkdir .ssh ; cat >> .ssh/authorized_keys" || exit 1

cat <<EOF
Now try logging into the machine, with "ssh '${1%:}'", and check in:

  .ssh/authorized_keys

to make sure we haven't added extra keys that you weren't expecting.

EOF

Go forth, copy, paste, chmod and happily deploy your ssh keys with ease.

P.S. For those who don’t know what I mean by chmod, see the following.

chmod +x ./ssh-copy-id

Bash Parallel Execution

If you have ever wanted an easy way to execute multiple jobs in parallel in bash, then this is the snippet for you. This was originally posted on Stack Exchange. It has been modified a bit.

#!/bin/bash

#how many jobs to run at one time
JOBS_AT_ONCE=20

# The bgxupdate and bgxlimit functions below allow for
# running X jobs in parallel in bash.  They are taken from:
# http://stackoverflow.com/questions/1537956/bash-limit-the-number-of-concurrent-jobs/1685440#1685440

# bgxupdate - update active processes in a group.
#   Works by transferring each process to new group
#   if it is still active.
# in:  bgxgrp - current group of processes.
# out: bgxgrp - new group of processes.
# out: bgxcount - number of processes in new group.

bgxupdate() {
    bgxoldgrp=${bgxgrp}
    bgxgrp=""
    ((bgxcount = 0))
    bgxjobs=" $(jobs -pr | tr '\n' ' ')"
    for bgxpid in ${bgxoldgrp} ; do
        echo "${bgxjobs}" | grep " ${bgxpid} " >/dev/null 2>&1
        if [[ $? -eq 0 ]] ; then
            bgxgrp="${bgxgrp} ${bgxpid}"
            ((bgxcount = bgxcount + 1))
        fi
    done
}

# bgxlimit - start a sub-process with a limit.

#   Loops, calling bgxupdate until there is a free
#   slot to run another sub-process. Then runs it
#   an updates the process group.
# in:  $1     - the limit on processes.
# in:  $2+    - the command to run for new process.
# in:  bgxgrp - the current group of processes.
# out: bgxgrp - new group of processes

bgxlimit() {
    bgxmax=$1 ; shift
    bgxupdate
    while [[ ${bgxcount} -ge ${bgxmax} ]] ; do
        sleep 1
        bgxupdate
    done
    if [[ "$1" != "-" ]] ; then
        $* &
        bgxgrp="${bgxgrp} $!"
    fi
}

bgxgrp="process_group_1"
for LINE in `cat hosts`
do
    CHECK_SCRIPT='echo $(hostname),$(cat /etc/debian_version)'
    bgxlimit $JOBS_AT_ONCE ssh ${LINE} "${CHECK_SCRIPT}"
done
# Wait until all queued processes are done.

bgxupdate
while [[ ${bgxcount} -ne 0 ]] ; do
    oldcount=${bgxcount}
    while [[ ${oldcount} -eq ${bgxcount} ]] ; do
        sleep 1
        bgxupdate
    done
done

In this script the primary changes are defining the max number of simultaneous jobs, as well as doing somewhat useful work in returning the hostname and the debian version.

Turboprop

As an extension of my previous post on parallel execution I present turboprop. The initial version of this script will perform an optimization of a mysql database with multiple tables running at the same time. In the future it may be extended to allow for more operations from the command line such as mysql dumps.

#!/bin/bash
# turboprop

# how many jobs to run at one time
JOBS_AT_ONCE=20
# Command to run in parallel in this case mysqlcheck -o
COMMAND="mysqlcheck -o"

# Print usage information and exit
print_usage(){
    echo -e "\n" \
    "usage: ./turboprop -d databasename \n" \
    "Optimizes mysql tables in parallel\n" \
    "-d <databasename>      Database to optimize\n" \
    "-h                     this help\n" \
    "\n" && exit 1
}

###
# Options
###

# Loop through $@ to find flags
while getopts ":d:" FLAG; do
    case "${FLAG}" in
        d) # Database name
            DB=${OPTARG} ;;
        h) # Print usage
            print_usage;;
        [:?]) print_usage;;
    esac
done

[ ! ${DB} ] && print_usage

###
# Functions
###

# The bgxupdate and bgxlimit functions below allow for
# running X jobs in parallel in bash.  They are taken from:
# http://stackoverflow.com/questions/1537956/bash-limit-the-number-of-concurrent-jobs/1685440#1685440

# bgxupdate - update active processes in a group.
#   Works by transferring each process to new group
#   if it is still active.
# in:  bgxgrp - current group of processes.
# out: bgxgrp - new group of processes.
# out: bgxcount - number of processes in new group.

bgxupdate() {
    bgxoldgrp=${bgxgrp}
    bgxgrp=""
    ((bgxcount = 0))
    bgxjobs=" $(jobs -pr | tr '\n' ' ')"
    for bgxpid in ${bgxoldgrp} ; do
        echo "${bgxjobs}" | grep " ${bgxpid} " >/dev/null 2>&1
        if [[ $? -eq 0 ]] ; then
            bgxgrp="${bgxgrp} ${bgxpid}"
            ((bgxcount = bgxcount + 1))
        fi
    done
}

# bgxlimit - start a sub-process with a limit.

#   Loops, calling bgxupdate until there is a free
#   slot to run another sub-process. Then runs it
#   an updates the process group.
# in:  $1     - the limit on processes.
# in:  $2+    - the command to run for new process.
# in:  bgxgrp - the current group of processes.
# out: bgxgrp - new group of processes

bgxlimit() {
    bgxmax=$1 ; shift
    bgxupdate
    while [[ ${bgxcount} -ge ${bgxmax} ]] ; do
        sleep 1
        bgxupdate
    done
    if [[ "$1" != "-" ]] ; then
        $* &
        bgxgrp="${bgxgrp} $!"
    fi
}

###
# Program Execution
###

bgxgrp="process_group_1"
for TABLE in `mysql ${DB} -e 'show tables'`
do
    bgxlimit ${JOBS_AT_ONCE} ${COMMAND} ${TABLE}
done

# Wait until all queued processes are done.

bgxupdate
while [[ ${bgxcount} -ne 0 ]] ; do
    oldcount=${bgxcount}
    while [[ ${oldcount} -eq ${bgxcount} ]] ; do
        sleep 1
        bgxupdate
    done
done

Bash Best(ish) practices part 4

This is the final post in the series. I will follow up this post by commiting my code to github for easy access.

#!/bin/bash
# bash template

# Print usage information and exit
print_usage(){
    echo -e "\n" \
    "usage: ./bashtemplate -o option \n" \
    "\n" \
    "-o <option>    an option\n" \
    "-h             this help\n" \
    "\n" && exit 1
}

###
# Options
###

# Loop through $@ to find flags
while getopts ":ho:" FLAG; do
    case "${FLAG}" in
        o) # Our option
            OPTION="${OPTARG}" ;;
        h) # Print usage information
            HELP=1;;
        [:?]) # Print usage information
            print_usage;;
    esac
done

###
# Functions
###

log_date(){
    echo $(date +"%b %e %T")
}

error() {
    NOW=$(log_date)
    echo "${NOW}: ERROR: $1"
    exit 1
}

warning() {
    NOW=$(log_date)
    echo "${NOW}: WARNING: $1"
}

info() {
    NOW=$(log_date)
    echo "${NOW}: INFO: $1"
}

# Do something
do_something(){
    info "Doing something..."
    warning "Encountered an anomaly while doing something."
}

###
# Program execution
###

[ "${HELP}" ] && print_usage

if [ ${OPTION} ]
then
    info "Executing do_something()"
    do_something
else
    print_usage
fi

I have removed much of the extraneous stuff from the previous examples as I would like this to be generally useful for writing scripts.

The final element to cover is debugging. In bash the following options are available:

-e Stops execution if any external program returns non-zero.

-x Trace what is being executed.

-n Don’t execute. Useful for syntax errors.

These can also be turned on and off in the script with “set”.

Lets see them in action.

-x

$ bash -x ./bashtemplate
+ getopts :ho: FLAG
+ '[' '' ']'
+ '[' ']'
+ print_usage
+ echo -e '\n' 'usage: ./bashtemplate -o option \n' '\n' '-o <option>    an option\n' '-h             this help\n' '\n'

 usage: ./bashtemplate -o option

 -o <option>    an option
 -h             this help


+ exit 1

-n I introduced a syntax error.

$ bash -n ./bashtemplate
./bashtemplate: line 70: syntax error near unexpected token `else'
./bashtemplate: line 70: `else'

-e I added a grep that would fail followed by an echo statement.

$ bash -e ./bashtemplate
$

I hope this series has proven useful for someone other than myself, but if it has not I will never know.

https://github.com/jsutton/bashtemplate

Bash Best(ish) practices part 3
Bash Best(ish) practices part 2
Bash Best(ish) practices part 1

Bash Best(ish) practices part 3

This is our third installment of the best(ish) series. In this installment we will look at some basic logging functions that simplify life. Here is the code so far.

#!/bin/bash
# Copy some files

# Print usage information and exit
print_usage(){
    echo -e "\n" \
    "usage: ./copy_stuff -s sourcedir -d destdir \n" \
    "\n" \
    "-s <sourcedir> source directory\n" \
    "-d <destdir>   destination directory\n" \
    "-h             this help\n" \
    "\n" && exit 1
}

###
# Options
###

# Loop through $@ to find flags
while getopts ":hs:d:" FLAG; do
    case "${FLAG}" in
        s) # Our source
            SOURCE="${OPTARG}" ;;
        d) # Our destination
            DEST="${OPTARG}" ;;
        h) # Print usage information
            HELP=1;;
        [:?]) # Print usage information
            print_usage;;
    esac
done

###
# Functions
###

log_date(){
    echo $(date +"%b %e %T")
}

error() {
    NOW=$(log_date)
    echo "${NOW}: ERROR: $1"
    exit 1
}

warning() {
    NOW=$(log_date)
    echo "${NOW}: WARNING: $1"
}

info() {
    NOW=$(log_date)
    echo "${NOW}: INFO: $1"
}

# Copy some files from one place to another
copy_files(){
    info "Copying ${SOURCE}/foo to ${DEST}/foo"
    info "Copying ${SOURCE}/bar to ${DEST}/bar"
    info "Copying ${SOURCE}/baz/foo to ${DEST}/baz/foo"
    info "Copying ${SOURCE}/baz/bar/foo to ${DEST}/baz/bar/foo"
    info "Copying ${SOURCE}/dir/somefile to ${DEST}/dir/someotherfile"
    echo
}

# Restart some service
restart_service(){
    info "Stopping the service"
    info "Making sure the service is stopped"
    info "Reticulating splines..."
    warning "splines not reticulated"
    info "Starting the service"
    echo
}

# Test to see if the service is running
test_service(){
    info "service is tested"
}

###
# Program execution
###

[ "${HELP}" ] && print_usage

if [ ${SOURCE} ] && [ ${DEST} ]
then
    info "Copying files from ${SOURCE} to ${DEST}"
    copy_files
    info "Restarting the service"
    restart_service
    info "Testing the service"
    test_service
fi

Lets break it down…

log_date(){
    echo $(date +"%b %e %T")
}

error() {
    NOW=$(log_date)
    echo "${NOW}: ERROR: $1"
    exit 1
}

warning() {
    NOW=$(log_date)
    echo "${NOW}: WARNING: $1"
}

info() {
    NOW=$(log_date)
    echo "${NOW}: INFO: $1"
}

We have some new friends in the form of error,warning and info. These functions simply tag some provided text with an indicator of the type of output and also provide timestamp information. They save us just enough time to prove useful as we will see in a moment.

    info "Reticulating splines..."
    warning "splines not reticulated"

You can see here that all we have to do is specify the logger we want to use and the particular output.

Let’s see it run.

# ./copy_stuff -s /dir1 -d /dir2
Mar 9 09:16:00: INFO: Copying files from /dir1 to /dir2
Mar 9 09:16:00: INFO: Copying /dir1/foo to /dir2/foo
Mar 9 09:16:01: INFO: Copying /dir1/bar to /dir2/bar
Mar 9 09:16:01: INFO: Copying /dir1/baz/foo to /dir2/baz/foo
Mar 9 09:16:01: INFO: Copying /dir1/baz/bar/foo to /dir2/baz/bar/foo
Mar 9 09:16:01: INFO: Copying /dir1/dir/somefile to /dir2/dir/someotherfile

Mar 9 09:16:01: INFO: Restarting the service
Mar 9 09:16:01: INFO: Stopping the service
Mar 9 09:16:01: INFO: Making sure the service is stopped
Mar 9 09:16:01: INFO: Reticulating splines...
Mar 9 09:16:01: WARNING: splines not reticulated
Mar 9 09:16:01: INFO: Starting the service

Mar 9 09:16:01: INFO: Testing the service
Mar 9 09:16:01: INFO: service is tested

Nice output. It is also worth noting that now you can simply change the functions to modify all of the output of your script. This might be useful if you wanted to change the date format for instance.

For more information on date formatting see http://www.cyberciti.biz/faq/linux-unix-formatting-dates-for-display/

Bash Best(ish) practices part 4
Bash Best(ish) practices part 2
Bash Best(ish) practices part 1

Bash Best(ish) practices part 2

Part 2 of our series has us beginning to use functions as a part of script development. Bash functions are pretty simple, and much has already been written about them. Today we will primarly be concerned with how they can help your script flow better and potentially speed up development and troubleshooting.

Lets start with some code…

#!/bin/bash
# Copy some files

# Print usage information and exit
print_usage(){
    echo -e "\n" \
    "usage: ./copy_stuff -s sourcedir -d destdir \n" \
    "\n" \
    "-s <sourcedir> source directory\n" \
    "-d <destdir>   destination directory\n" \
    "-h             this help\n" \
    "\n" && exit 1
}

# Loop through $@ to find flags
while getopts ":hs:d:" FLAG; do
    case "${FLAG}" in
        s) # Our source
            SOURCE="${OPTARG}" ;;
        d) # Our destination
            DEST="${OPTARG}" ;;
        h) # Print usage information
            HELP=1;;
        [:?]) # Print usage information
            print_usage;;
    esac
done

# Copy some files from one place to another
copy_files(){
    echo "Copying ${SOURCE}/foo to ${DEST}/foo"
    echo "Copying ${SOURCE}/bar to ${DEST}/bar"
    echo "Copying ${SOURCE}/baz/foo to ${DEST}/baz/foo"
    echo "Copying ${SOURCE}/baz/bar/foo to ${DEST}/baz/bar/foo"
    echo "Copying ${SOURCE}/dir/somefile to ${DEST}/dir/someotherfile"
}

# Restart some service
restart_service(){
    echo "Stopping the service"
    echo "Making sure the service is stopped"
    echo "Reticulating splines..."
    echo "Starting the service"
}

# Test to see if the service is running
test_service(){
    echo "service is tested"
}

[ "${HELP}" ] && print_usage

if [ ${SOURCE} ] && [ ${DEST} ]
then
    echo "Copying files from ${SOURCE} to ${DEST}"
    copy_files
    echo "Restarting the service"
    restart_service
    echo "Testing the service"
    test_service
fi

There is a lot happening in this script, but you can see that we are expanding upon ideas from last week.

Lets break down the key elements.

print_usage(){
    echo -e "\n" \
    "usage: ./copy_stuff -s sourcedir -d destdir \n" \
    "\n" \
    "-s <sourcedir> source directory\n" \
    "-d <destdir>   destination directory\n" \
    "-h             this help\n" \
    "\n" && exit 1
}

We define a function called print_usage which, you guessed it, prints the usage information for our script. I cannot understate how usefull this is when you are expecting your script to be run by other people. Please don’t write scripts that by default just do something or use the -h flag to perform some operation other than printing usage.

The format used here allows for more readable help.

[:?]) # Print usage information
    print_usage;;

The second thing you should notice is that we have added a new option check. : and ? are triggered if you fail to provide an argument to a flag that requires one or you provide a flag that does not exist. In this case we want to let the user know that they do not know how to invoke our program and they should read the “friendly” manual.

[ "${HELP}" ] && print_usage

This line was added to preempt other operations from taking place if you have specified the -h flag.

# Copy some files from one place to another
copy_files(){
    echo "Copying ${SOURCE}/foo to ${DEST}/foo"
    echo "Copying ${SOURCE}/bar to ${DEST}/bar"
    echo "Copying ${SOURCE}/baz/foo to ${DEST}/baz/foo"
    echo "Copying ${SOURCE}/baz/bar/foo to ${DEST}/baz/bar/foo"
    echo "Copying ${SOURCE}/dir/somefile to ${DEST}/dir/someotherfile"
}

# Restart some service
restart_service(){
    echo "Stopping the service"
    echo "Making sure the service is stopped"
    echo "Reticulating splines..."
    echo "Starting the service"
}

# Test to see if the service is running
test_service(){
    echo "service is tested"
}

The next thing you should see is that we put all of the work that the script is doing within function declarations. As you will see below, this allows us to implement and test each component separately.

if [ ${SOURCE} ] && [ ${DEST} ]
then
    echo "Copying files from ${SOURCE} to ${DEST}"
    copy_files
    echo "Restarting the service"
    restart_service
    echo "Testing the service"
    test_service
fi

It should also become apparent that you can more easily wrap function calls in argument checks which can help your script be more readable. If we want to perform work on the test service function, we can simply comment out the other three calls.

Let’s see this script in action.

# ./copy_stuff -h

 usage: ./copy_stuff -s sourcedir -d destdir

 -s <sourcedir> source directory
 -d <destdir>   destination directory
 -h             this help 

# ./copy_stuff -h -s /dir1

 usage: ./copy_stuff -s sourcedir -d destdir

 -s <sourcedir> source directory
 -d <destdir>   destination directory
 -h             this help

# ./copy_stuff -h -s /dir1 -d /dir2

 usage: ./copy_stuff -s sourcedir -d destdir

 -s <sourcedir> source directory
 -d <destdir>   destination directory
 -h             this help

# ./copy_stuff -s /dir1 -d /dir2
Copying files from /dir1 to /dir2
Copying /dir1/foo to /dir2/foo
Copying /dir1/bar to /dir2/bar
Copying /dir1/baz/foo to /dir2/baz/foo
Copying /dir1/baz/bar/foo to /dir2/baz/bar/foo
Copying /dir1/dir/somefile to /dir2/dir/someotherfile
Restarting the service
Stopping the service
Making sure the service is stopped
Reticulating splines...
Starting the service
Testing the service
service is tested

For more information regarding functions see http://www.cyberciti.biz/faq/bash-shell-script-function-examples/

Bash Best(ish) practices part 4
Bash Best(ish) practices part 3
Bash Best(ish) practices part 1

Bash Best(ish) practices part 1

Hello again. It has been some time since I have posted here. Bash is the focus of todays post. Well… Really it is the focus of several posts over the next few weeks as we delve into some best(ish) practices when writing bash scripts. The culmnination of these articles will be the creation of a bash script template file that can be used to make writing scripts a little faster/better.

Enter getopts… We will start with some code then break it down.

# Loop through $@ to find flags
while getopts ":hs:e:" FLAG; do
    case "${FLAG}" in
        s) # Here lies some flag
            OPTION1="${OPTARG}" ;;
        e) # Another flag, we will call this one fred
            FRED="${OPTARG}" ;;
        h) # A flag with no value
            HELP=1
            echo "You need some help. We will get to that later." ;;
    esac
done

Let’s break it down…

getopts ":hs:e:" FLAG

The format of getopts is

getopts OPTSTRING VARNAME

Where:

OPTSTRING – is what getopts should expect as an option and whether to expect an argument to that option. It also allows the specification of how to deal with errors.

VARNAME – is used to report back the option that was found.

Options are specified as simply the character you are looking for(sorry no long options supported). If you want to retreive the argument immediatly following the flag, you may place a : after the option.

Error handling defaults to verbose and can be switched to silent by preceding the OPTSTRING with a :

In the event of an error, VARNAME will be set to ? to indicate an invalid option and : to indicate that a required argument was not found.

getopts is usually run in a while loop so that it can process all of $@.

case "${FLAG}" in

A case statement is used to evaluate which optino we are dealing with, although you could use if/then if you so chose.

s) # Here lies some flag
    OPTION1="${OPTARG}" ;;
e) # Another flag, we will call this one fred
    FRED="${OPTARG}" ;;
h) # A flag with no value
    HELP=1
    echo "You need some help. We will get to that later." ;;

You can perform whatever operations you want based on the option chosen, although it is common to either set a variable or run a function such as print_usage.

How do we put this together into something useful?

# ./foo -h
You need some help. We will get to that later.
# ./foo -s hello
hello world
# ./foo -s
# ./foo -s goodby
#

For more information see http://wiki.bash-hackers.org/howto/getopts_tutorial

Have fun…

Bash Best(ish) practices part 4
Bash Best(ish) practices part 3
Bash Best(ish) practices part 2

Net::SSH::Perl, Perlbrew and You

For all of the admins who threw their computer out the window and swore they would never use CPAN again when they tried to install Net::SSH::Perl, try the following two things:
1. Install the GMP library(not the perl one) apt-get install libgmp-dev
2. Use cpanm instead of cpan. i.e. cpanm Net::SSH::Perl

This should solve the following error:
GMP.xs:4:17: error: gmp.h: No such file or directory

Also, while I am talking about it, check out perlbrew. It allows you to easily set up self contained instances of perl for multiple versions and switch between them. The possible uses for this are nearly endless, but I find that it increases my “sense of adventure” when messing with perl modules and my environment since I am not affecting the system perl.

Perlbrew