Differences between revisions 4 and 21 (spanning 17 versions)
Revision 4 as of 2009-12-07 01:37:29
Size: 3168
Editor: ozgw
Comment:
Revision 21 as of 2014-08-13 06:13:22
Size: 1804
Editor: 94
Comment:
Deletions are marked like this. Additions are marked like this.
Line 2: Line 2:
== How do I process options in a bash script? ==

For example, how do I code my bash script to accept a bunch of options like
== Common utility functions (warn, die) ==
(If you were looking for option processing, see [[BashFAQ/035]].) The following functions are frequently asked for in #bash, so we hope you find them useful.
Line 7: Line 6:
foobar -a --include something ##
# warn: Print a message to stderr.
# Usage: warn "format" ["arguments"...]
#
warn() {
  local fmt="$1"
  shift
  printf "script_name: $fmt\n" "$@" >&2
}

###
### The following three "die" functions
### depend on the above "warn" function.
###

##
# die (simple version): Print a message to stderr
# and exit with the exit status of the most recent
# command.
# Usage: some_command || die "message" ["arguments"...]
#
die () {
  local st="$?"
  warn "$@"
  exit "$st"
}

##
# die (explicit status version): Print a message to
# stderr and exit with the exit status given.
# Usage: if blah; then die status_code "message" ["arguments"...]; fi
#
die() {
  local st="$1"
  shift
  warn "$@"
  exit "$st"
}

##
# die (optional status version): Print a message to
# stderr and exit with either the given status or
# that of the most recent command.
# Usage: some_command || die [status code] "message" ["arguments"...]
#
die() {
  local st="$?"
  if [[ "$1" != *[^0-9]* ]]; then
    st="$1"
    shift
  fi
  warn "$@"
  exit "$st"
}
Line 10: Line 62:
First up, there are some [[http://www.gnu.org/software/libtool/manual/libc/Argument-Syntax.html|GNU and POSIX standards]] for how to do this.

=== do-it-yourself ===
Line 15: Line 64:
while "$1"; do
    case "$1" in
        -a|--all) ALL=yes ;shift ;;
        -i|--include) INCLUDE="$2"; shift; shift ;;
        *) echo "$PROG: Bad option '$1', exiting." >&2; exit 1;;
    esac
done
##
# warn: Print a message to stderr.
# Usage: warn "message"
#
warn() {
  printf '%s\n' "${BASH_SOURCE##*/}: $@" >&2
}

##
# die (optional status version): Print a message to
# stderr and exit with either the given status or
# that of the most recent command.
# Usage: some_command || die "message" [status code]
#

die() {
  local st=$?
  case $2 in
    *[^0-9]*|'') :;;
    *) st=$2;;
  esac
  warn "$1"
  exit "$st"
}
Line 23: Line 89:

This is all well and good but it's crufty and it doesn't honour the standards - for example, how do you handle concatenation of single-letter options, how about if the user writes {{{-isomething}}} without a space or {{{--include=something}}}?

=== getopt(1) ===

{{{
TEMP=`getopt -o ab:c:: --long a-long,b-long:,c-long:: -n 'example.bash' -- "$@"`

if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi

eval set -- "$TEMP"

while true ; do
        case "$1" in
                -a|--a-long) echo "Option a" ; shift ;;
        .../etc
}}}

This is better as it obeys the standards and gives the user a fairly predictable user-interface. There is still the disadvantage that options are coded in at least 2, probably 3 places - in the call to getopt(1), in the case statement that processes them and presumably in the help message that you are going to get around to writing one of these days. This is a classic opportunity for errors to creep in as the code is written and maintained - often not discovered till much, much later.

=== process-getopt(1) ===

{{{
PROG=$(basename $0)
VERSION='1.2'
USAGE="A tiny example using process-getopt(1)"

# call process-getopt functions to define some options:
source /usr/bin/process-getopt

SLOT=""
SLOT_func() { [ "${1:-""}" ] && SLOT="yes"; } # callback for SLOT option
add_opt SLOT "boolean option" s "" slot

TOKEN=""
TOKEN_func() { [ "${1:-""}" ] && TOKEN="$2"; } # callback for TOKEN option
add_opt TOKEN "this option takes a value" t n token number

add_std_opts # define the standard options --help etc:

TEMP=$(call_getopt "$@") || exit 1
eval set -- "$TEMP" # just as with getopt(1)

# remove the options from the command line
process_opts "$@" || shift "$?"

echo "SLOT=$SLOT"
echo "TOKEN=$TOKEN"
echo "args=$@"
}}}

Here, all information about each option is defined in once place making for much easier authoring and maintainence. A lot of the dirty work is handled automatically and standards are obeyed as in getopt(1) - because it calls getopt for you. As an added bonus you get a nicely formatted help page (for {{{-h, --help}}} and a starter for a man page (using an easter-egg option {{{ --print-man-page }}} ).

[[http://sourceforge.net/projects/process-getopt/ | process-getopt ]] at sourceforge

[[http://bhepple.freeshell.org/oddmuse/wiki.cgi/process-getopt | author's website ]]

Full disclosure: bhepple at freeshell dot org wrote this entry and is the autor of process-getopt(1). Constructive criticism welcome!!

Common utility functions (warn, die)

(If you were looking for option processing, see BashFAQ/035.) The following functions are frequently asked for in #bash, so we hope you find them useful.

##
# warn: Print a message to stderr.
# Usage: warn "format" ["arguments"...]
#
warn() {
  local fmt="$1"
  shift
  printf "script_name: $fmt\n" "$@" >&2
}

###
### The following three "die" functions
### depend on the above "warn" function.
###

##
# die (simple version): Print a message to stderr
# and exit with the exit status of the most recent
# command.
# Usage: some_command || die "message" ["arguments"...]
#
die () {
  local st="$?"
  warn "$@"
  exit "$st"
}

##
# die (explicit status version): Print a message to
# stderr and exit with the exit status given.
# Usage: if blah; then die status_code "message" ["arguments"...]; fi
#
die() {
  local st="$1"
  shift
  warn "$@"
  exit "$st"
}

##
# die (optional status version): Print a message to
# stderr and exit with either the given status or
# that of the most recent command.
# Usage: some_command || die [status code] "message" ["arguments"...]
#
die() {
  local st="$?"
  if [[ "$1" != *[^0-9]* ]]; then
    st="$1"
    shift
  fi
  warn "$@"
  exit "$st"
}

##
# warn: Print a message to stderr.
# Usage: warn "message"
#
warn() {
  printf '%s\n' "${BASH_SOURCE##*/}: $@" >&2
}

##
# die (optional status version): Print a message to
# stderr and exit with either the given status or
# that of the most recent command.
# Usage: some_command || die "message" [status code]
#

die() {
  local st=$?
  case $2 in
    *[^0-9]*|'') :;;
    *) st=$2;;
  esac
  warn "$1"
  exit "$st"
} 


CategoryShell

BashFAQ/101 (last edited 2020-02-04 18:35:41 by GreyCat)