Command line arguments in bash scripts

This is something that has always annoyed me about bash scripts… The fact that it’s difficult to run

/path/to/script.sh --foo=bar -v -n 10 blah -one='last arg'

So I decided to write up a bash function that let me easily (once the function was complete) access this type of information. And because I like sharing, here it is:

#!/bin/bash
function getopt() {
  var=""
  wantarg=0
  for (( i=1; i< =$#; i+=1 )); do
    lastvar=$var
    var=${!i}
    if [ "$var" = "" ]; then
        continue
    fi
    echo \ $var | grep -q -- '='
    if [ $? -eq 0 ]; then
      ## -*param=value
      var=$(echo \ $var | sed -r s/'^[ ]*-*'/''/)
      myvar=${var%=*}
      myval=${var#*=}
      eval "${myvar}"="'$myval'"
    else
      echo \ $var | grep -E -q -- '^[ ]*-'
      if [ $? -eq 0 ]; then
        # -*param$
        var=$(echo \ $var | sed -r s/'^[ ]*-*'/''/)
        eval "${var}"=1
        wantarg=1
      else
        echo \ $var | grep -E -- '^[ ]*-'
        if [ $? -eq 0 ]; then
          # the current one has a dash, so cannot be
          # the argument to the last parameter
          wantarg=0
        fi
        if [ $wantarg -eq 1 ]; then
          # parameter argument
          val=$var
          var=$lastvar
          eval "${var}"="'${val}'"
          wantarg=0
        else
          # parameter
          if [ "${!var}" = "" ]; then
            eval "${var}"=1
          fi
          wantarg=0
        fi
      fi
    fi
  done
}

OIFS=$IFS; IFS=$(echo -e "\n"); getopt $@; IFS=$OIFS

now at this point (assuming the above command line parameter and script) I should have access to the following variables: $foo ("bar") $v (1) $n (10) $blah (1) $one ("last arg"), like so:

OIFS=$IFS; IFS=$(echo -e "\n"); getopt $@; IFS=$OIFS

echo -e "
foo:\t$foo
v:\t$v
n:\t$n
blah:\t$blah
one:\t$one
"

You might be curious about this line:

OIFS=$IFS; IFS=$(echo -e "\n"); getopt $@; IFS=$OIFS

IFS is the variable that tells bash how strings are separated (and mastering its use will go a long way towards enhancing your bash scripting skills.) Anyhow, by default IFS=” ” which normally is OK, but in our case we dont want “last arg” to be two seperate strings, but one. I cannot put the IFS assignment inside the function because by that point bash has already split the variable, it needs to be done at a level of the script in which $@ has not been touched yet. So I store the current IFS variable in $OIFS (Old IFS) and set IFS to a newline character. After running the function we reassign IFS to what it was beforehand. This is because I dont know what you might be doing with your IFS. There are lots of reasons you might have already assigned it to something else, and I wouldnt want to break your flow. So we do the polite thing.

And in case the above gets munged for some reason you can see the plain text version here: bash-getopt/getopt.sh

Anyways, hope this helps someone out. If not it’s still here for me when *I* need it ;)

Comments (11)

  1. Milian Wolff wrote::

    Hi there, thanks for the snippet. I use it for a shell script but I found a error / shortcomming:

    <code>

    run.sh –file "foobar" –arguments "–foo"

    </code>

    This will fail with "line XY: foo: command not found", where XY points to the line:

    eval "${var}"=1

    (i.e. the first occurence of that, i.e. after var=$(echo $var | sed -r s/'^[ ]*-*'/''/) )

    Could you possibly fix this?

    PS: You might want to setup a GeSHi plugin for wordpress, that way your script will get highlighted properly. If not – drop me a mail, I'm a developer there :)

    Thank you!

    Sunday, July 20, 2008 at 3:46 AM #
  2. Bash has a built in function called getopts which does help with parsing command line arguement (help getopts for more info), but your solution seems to be a heck of a lot easier to use.

    Thanks for sharing!

    -Jeremy

    Tuesday, July 22, 2008 at 7:33 AM #
  3. bothie wrote::

    Two comments:

    1. Instead of

    OIFS=$IFS; IFS=$(echo -e "n"); getopt $@; IFS=$OIFS

    you could also just write

    getopt "$@"

    2. It may be a security problem if the getopt function may write any variable in the normal namespace of the script. So, it's better to prefix each variable by e.g. "arg_". So –name bothie won't get name="bothie" but arg_name="bothie".

    Regards, Bodo

    Wednesday, August 20, 2008 at 10:30 PM #
  4. bothie wrote::

    Oh, and another thing.

    @Milian Wolff:

    It doesn't make a difference if you write

    run.sh –file "foobar" –arguments "–foo"

    or if you write

    run.sh –file "foobar" –arguments –foo

    For this reason the script should be updates to allow for arguments like "–arguments=–foo"

    Regards, Bodo

    Wednesday, August 20, 2008 at 10:33 PM #
  5. bothie wrote::

    >>> For this reason the script should be updates to allow for arguments like “–arguments=–foo”

    Watching closer on the script I discovered, that the script already support that style of arguments.

    However, it doesn't support other things. Imagine

    mv -i oldname newname

    This would lead to i=oldname and newname -> ???

    In short: This script helps in parsing command line options, but it isn't the non-plus-ultra yet.

    Regards, Bodo

    Friday, August 22, 2008 at 12:03 AM #
  6. Doug wrote::

    This is a pretty retarded and feature-lacking re-invention of the wheel. Seriously:

    help getopt;

    It's a bash built-in (and mostly compatible binaries that the built-in was based off of exist on any sane system even without bash!).

    Example usage:

    # Parse commandline options.

    GETOPT="$(getopt -o c:olhdfs –long children:,stdout,syslog,help,dry-run,force,status -n "$0" — "$@")"

    if [ "$?" -gt '0' ]; then

    echo "$USAGE"

    exit 1

    fi

    eval set — "$GETOPT"

    while true; do

    case "$1" in

    -h|–help) echo "$USAGE"; exit 0 ;;

    -c|–children) if [ "$2" -gt '0' ] >/dev/null 2>&1; then

    MAXCHILDREN="$2";

    shift;

    shift;

    else

    echo "Number of children MUST be an integer.";

    exit 1;

    fi

    ;;

    -o|–stdout) TTYOUT=1; shift ;;

    -l|–syslog) SYSLOGOUT=1; shift ;;

    -d|–dry-run) TTYOUT=1; DRYRUN=1; shift ;;

    -s|–status) TTYOUT=1; STATUSONLY=1; shift ;;

    -f|–force) FORCE=1; shift ;;

    –) shift; break ;;

    *) echo "Invalid Option ${1}."; echo "$USAGE"; exit 1 ;;

    esac

    done

    Thursday, November 13, 2008 at 1:55 AM #
  7. fabricio wrote::

    Here's yet another take on it:

    http://codesnippets.joyent.com/posts/show/1697

    Sunday, November 16, 2008 at 11:41 PM #
  8. apokalyptik wrote::

    @Doug — Why thanks. I especially appreciate the retarded bit. Cheers!

    Tuesday, November 18, 2008 at 4:24 AM #
  9. Toki wrote::

    Thank you:)

    Wednesday, December 10, 2008 at 5:29 AM #
  10. anonymous wrote::

    He's right, y'know.

    getopt works quite marvelously, and re-inventing the wheel is pretty retarded.

    Six levels of nesting, just to parse parameters looks like a maintainability nightmare, compared to a single, simple while/case loop.

    Sunday, January 25, 2009 at 7:18 AM #
  11. chilicuil wrote::

    Hey dude, thanks a lot for sharing this peace of goodness, I've been dealing with getopts and getopt and I still can't believe how bash has no proper built-in functions to deal with all kind of options. getopts is great, but I really wanna be able to use –long-options. There are a lot of libraries who try to fix this issue but so far this one is the most easy and easy to integrate (I don't wanna source any other file!) that I've found, I wanna focus in my code not in handling options.

    Thanks again! :)

    Tuesday, June 29, 2010 at 4:02 PM #

Trackback/Pingback (1)

  1. Bookmarks about Scripts on Friday, September 26, 2008 at 7:15 PM

    [...] – bookmarked by 1 members originally found by showwhat on 2008-09-09 Command line arguments in bash scripts http://blog.apokalyptik.com/2008/07/07/command-line-arguments-in-bash-scripts/ – bookmarked by 6 [...]