Elegant commandline argument parsing on the shell

Parsing command line arguments on the shell is often done in an ad-hoc fashion, growing unwieldy as time goes by, but there are tools to make that elegant. Here’s a complete example.

I use this in the conf project (easy setup of autotools projects). It builds on the great solution by Adam Katz.

# outer loop to allow processing option arguments at the end
while test ! $# -eq 0; do
    # getopts loop, here you define the short options: 
    # h for -h, l: for -l <lang>. -: provides support for long-options.
    while getopts -- hl:-: arg "$@"; do
        case $arg in
            h ) ARG_HELP=true ;;
            l ) ARG_LANG="$OPTARG" ;;
            - ) LONG_OPTARG="${OPTARG#*=}"
                case "$OPTARG" in
                    help    ) ARG_HELP=true;;
                    lang=?* ) ARG_LANG="$LONG_OPTARG" ;;
                    # FIXME: using the same option twice (either both
                    # after the argument or both before it) gives the
                    # first, not the second value
                    lang*   ) ARG_LANG="${@:$OPTIND:1}" ; OPTIND=$(($OPTIND + 1));;
                    vcs=?*  ) ARG_VCS="$LONG_OPTARG" ;;
                    vcs*    ) ARG_VCS="${@:$OPTIND:1}" ; OPTIND=$(($OPTIND + 1));;
                    '' )      break ;; # "--" terminates argument
                                       # processing to allow giving
                                       # options for autogen.sh after
                                       # --
                    * )       echo "Illegal option --$OPTARG" >&2; exit 2;;
                esac;;
            \? ) exit 2 ;;  # getopts already reported the illegal
                            # option
        esac
    done
    shift $((OPTIND-1)) # remove parsed options and args from $@ list
    # reinitialize OPTIND to allow parsing again
    OPTIND=1
    # provide help output.
    if test x"${ARG_HELP}" = x"true"; then
        echo "${PROG} new [-h | --help] [-l | --lang <LANGUAGE>] [--vcs <VCS>] PROJECT_NAME"
        exit 0
    fi
    # get the argument
    if test x"${1}" = x"--"; then
        if test x"${PROJ}" = x""; then
            echo "Missing project name." >&2; exit 2
        else
            # nothing more to parse.
            # Remove -- from the remaining arguments
            shift 1
            break
        fi
    fi
    if test ! x"${1}" = x""; then
        PROJ="${1%/}" # without trailing slash
    fi
    # remove the argument, then continue the loop to allow putting
    # the options after the argument
    shift 1
done

Additional explanation for this is available from Adam Katz (2015). I’m allowed to include it here, because every answer on Stackoverflow is licensed under creativecommons attribution sharealike (cc by-sa) and because cc by-sa is upwards compatible to GPLv3.

# From Adam Katz, 2015: http://stackoverflow.com/users/519360/adam-katz
# Available at http://stackoverflow.com/a/28466267/7666
# License: cc by-sa: https://creativecommons.org/licenses/by-sa/3.0/
while getopts ab:c-: arg; do
  case $arg in
    a )  ARG_A=true ;;
    b )  ARG_B="$OPTARG" ;;
    c )  ARG_C=true ;;
    - )  LONG_OPTARG="${OPTARG#*=}"
         case $OPTARG in
           alpha    )  ARG_A=true ;;
           bravo=?* )  ARG_B="$LONG_OPTARG" ;;
           bravo*   )  echo "No arg for --$OPTARG option" >&2; exit 2 ;;
           charlie  )  ARG_C=true ;;
           alpha* | charlie* )
                       echo "No arg allowed for --$OPTARG option" >&2; exit 2 ;;
           '' )        break ;; # "--" terminates argument processing
           * )         echo "Illegal option --$OPTARG" >&2; exit 2 ;;
         esac ;;
    \? ) exit 2 ;;  # getopts already reported the illegal option
  esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list

With this and with the practical usage at the top you should be able to implement clean commandline parsing with ease.

Happy Hacking!

Inhalt abgleichen
Willkommen im Weltenwald!



Beliebte Inhalte

sn.1w6.org news