An example of how to use getopts in bash

前端 未结 7 2261
小蘑菇
小蘑菇 2020-11-22 07:35

I want to call myscript file in this way:

$ ./myscript -s 45 -p any_string

or

$ ./myscript -h  #should display         


        
相关标签:
7条回答
  • 2020-11-22 07:45

    "getops" and "getopt" are very limited. While "getopt" is suggested not to be used at all, it does offer long options. Where as "getopts" does only allow single character options such as "-a" "-b". There are a few more disadvantages when using either one.

    So i've written a small script that replaces "getopts" and "getopt". It's a start, it could probably be improved a lot.

    Update 08-04-2020: I've added support for hyphens e.g. "--package-name".

    Usage: "./script.sh package install --package "name with space" --build --archive"

    # Example:
    # parseArguments "${@}"
    # echo "${ARG_0}" -> package
    # echo "${ARG_1}" -> install
    # echo "${ARG_PACKAGE}" -> "name with space"
    # echo "${ARG_BUILD}" -> 1 (true)
    # echo "${ARG_ARCHIVE}" -> 1 (true)
    function parseArguments() {
      PREVIOUS_ITEM=''
      COUNT=0
      for CURRENT_ITEM in "${@}"
      do
        if [[ ${CURRENT_ITEM} == "--"* ]]; then
          printf -v "ARG_$(formatArgument "${CURRENT_ITEM}")" "%s" "1" # could set this to empty string and check with [ -z "${ARG_ITEM-x}" ] if it's set, but empty.
        else
          if [[ $PREVIOUS_ITEM == "--"* ]]; then
            printf -v "ARG_$(formatArgument "${PREVIOUS_ITEM}")" "%s" "${CURRENT_ITEM}"
          else
            printf -v "ARG_${COUNT}" "%s" "${CURRENT_ITEM}"
          fi
        fi
    
        PREVIOUS_ITEM="${CURRENT_ITEM}"
        (( COUNT++ ))
      done
    }
    
    # Format argument.
    function formatArgument() {
      ARGUMENT="${1^^}" # Capitalize.
      ARGUMENT="${ARGUMENT/--/}" # Remove "--".
      ARGUMENT="${ARGUMENT//-/_}" # Replace "-" with "_".
      echo "${ARGUMENT}"
    }
    
    0 讨论(0)
  • 2020-11-22 07:53

    POSIX 7 example

    It is also worth checking the example from the standard: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/getopts.html

    aflag=
    bflag=
    while getopts ab: name
    do
        case $name in
        a)    aflag=1;;
        b)    bflag=1
              bval="$OPTARG";;
        ?)   printf "Usage: %s: [-a] [-b value] args\n" $0
              exit 2;;
        esac
    done
    if [ ! -z "$aflag" ]; then
        printf "Option -a specified\n"
    fi
    if [ ! -z "$bflag" ]; then
        printf 'Option -b "%s" specified\n' "$bval"
    fi
    shift $(($OPTIND - 1))
    printf "Remaining arguments are: %s\n" "$*"
    

    And then we can try it out:

    $ sh a.sh
    Remaining arguments are: 
    $ sh a.sh -a
    Option -a specified
    Remaining arguments are: 
    $ sh a.sh -b
    No arg for -b option
    Usage: a.sh: [-a] [-b value] args
    $ sh a.sh -b myval
    Option -b "myval" specified
    Remaining arguments are: 
    $ sh a.sh -a -b myval
    Option -a specified
    Option -b "myval" specified
    Remaining arguments are: 
    $ sh a.sh remain
    Remaining arguments are: remain
    $ sh a.sh -- -a remain
    Remaining arguments are: -a remain
    

    Tested in Ubuntu 17.10, sh is dash 0.5.8.

    0 讨论(0)
  • 2020-11-22 07:54
    #!/bin/bash
    
    usage() { echo "Usage: $0 [-s <45|90>] [-p <string>]" 1>&2; exit 1; }
    
    while getopts ":s:p:" o; do
        case "${o}" in
            s)
                s=${OPTARG}
                ((s == 45 || s == 90)) || usage
                ;;
            p)
                p=${OPTARG}
                ;;
            *)
                usage
                ;;
        esac
    done
    shift $((OPTIND-1))
    
    if [ -z "${s}" ] || [ -z "${p}" ]; then
        usage
    fi
    
    echo "s = ${s}"
    echo "p = ${p}"
    

    Example runs:

    $ ./myscript.sh
    Usage: ./myscript.sh [-s <45|90>] [-p <string>]
    
    $ ./myscript.sh -h
    Usage: ./myscript.sh [-s <45|90>] [-p <string>]
    
    $ ./myscript.sh -s "" -p ""
    Usage: ./myscript.sh [-s <45|90>] [-p <string>]
    
    $ ./myscript.sh -s 10 -p foo
    Usage: ./myscript.sh [-s <45|90>] [-p <string>]
    
    $ ./myscript.sh -s 45 -p foo
    s = 45
    p = foo
    
    $ ./myscript.sh -s 90 -p bar
    s = 90
    p = bar
    
    0 讨论(0)
  • 2020-11-22 07:57

    The problem with the original code is that:

    • h: expects parameter where it shouldn't, so change it into just h (without colon)
    • to expect -p any_string, you need to add p: to the argument list

    Basically : after the option means it requires the argument.


    The basic syntax of getopts is (see: man bash):

    getopts OPTSTRING VARNAME [ARGS...]
    

    where:

    • OPTSTRING is string with list of expected arguments,

      • h - check for option -h without parameters; gives error on unsupported options;
      • h: - check for option -h with parameter; gives errors on unsupported options;
      • abc - check for options -a, -b, -c; gives errors on unsupported options;
      • :abc - check for options -a, -b, -c; silences errors on unsupported options;

        Notes: In other words, colon in front of options allows you handle the errors in your code. Variable will contain ? in the case of unsupported option, : in the case of missing value.

    • OPTARG - is set to current argument value,

    • OPTERR - indicates if Bash should display error messages.

    So the code can be:

    #!/usr/bin/env bash
    usage() { echo "$0 usage:" && grep " .)\ #" $0; exit 0; }
    [ $# -eq 0 ] && usage
    while getopts ":hs:p:" arg; do
      case $arg in
        p) # Specify p value.
          echo "p is ${OPTARG}"
          ;;
        s) # Specify strength, either 45 or 90.
          strength=${OPTARG}
          [ $strength -eq 45 -o $strength -eq 90 ] \
            && echo "Strength is $strength." \
            || echo "Strength needs to be either 45 or 90, $strength found instead."
          ;;
        h | *) # Display help.
          usage
          exit 0
          ;;
      esac
    done
    

    Example usage:

    $ ./foo.sh 
    ./foo.sh usage:
        p) # Specify p value.
        s) # Specify strength, either 45 or 90.
        h | *) # Display help.
    $ ./foo.sh -s 123 -p any_string
    Strength needs to be either 45 or 90, 123 found instead.
    p is any_string
    $ ./foo.sh -s 90 -p any_string
    Strength is 90.
    p is any_string
    

    See: Small getopts tutorial at Bash Hackers Wiki

    0 讨论(0)
  • 2020-11-22 08:01

    The example packaged with getopt (my distro put it in /usr/share/getopt/getopt-parse.bash) looks like it covers all of your cases:

    #!/bin/bash
    
    # A small example program for using the new getopt(1) program.
    # This program will only work with bash(1)
    # An similar program using the tcsh(1) script language can be found
    # as parse.tcsh
    
    # Example input and output (from the bash prompt):
    # ./parse.bash -a par1 'another arg' --c-long 'wow!*\?' -cmore -b " very long "
    # Option a
    # Option c, no argument
    # Option c, argument `more'
    # Option b, argument ` very long '
    # Remaining arguments:
    # --> `par1'
    # --> `another arg'
    # --> `wow!*\?'
    
    # Note that we use `"$@"' to let each command-line parameter expand to a 
    # separate word. The quotes around `$@' are essential!
    # We need TEMP as the `eval set --' would nuke the return value of getopt.
    TEMP=`getopt -o ab:c:: --long a-long,b-long:,c-long:: \
         -n 'example.bash' -- "$@"`
    
    if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
    
    # Note the quotes around `$TEMP': they are essential!
    eval set -- "$TEMP"
    
    while true ; do
        case "$1" in
            -a|--a-long) echo "Option a" ; shift ;;
            -b|--b-long) echo "Option b, argument \`$2'" ; shift 2 ;;
            -c|--c-long) 
                # c has an optional argument. As we are in quoted mode,
                # an empty parameter will be generated if its optional
                # argument is not found.
                case "$2" in
                    "") echo "Option c, no argument"; shift 2 ;;
                    *)  echo "Option c, argument \`$2'" ; shift 2 ;;
                esac ;;
            --) shift ; break ;;
            *) echo "Internal error!" ; exit 1 ;;
        esac
    done
    echo "Remaining arguments:"
    for arg do echo '--> '"\`$arg'" ; done
    
    0 讨论(0)
  • 2020-11-22 08:04

    Use getopt

    Why getopt?

    To parse elaborated command-line arguments to avoid confusion and clarify the options we are parsing so that reader of the commands can understand what's happening.

    What is getopt?

    getopt is used to break up (parse) options in command lines for easy parsing by shell procedures, and to check for legal options. It uses the GNU getopt(3) routines to do this.

    getopt can have following types of options.

    1. No-value options
    2. key-value pair options

    Note: In this document, during explaining syntax:

    • Anything inside [ ] is optional parameter in the syntax/examples.
    • is a place holder, which mean it should be substituted with an actual value.

    HOW TO USE getopt?

    Syntax: First Form

    getopt optstring parameters
    

    Examples:

    # This is correct
    getopt "hv:t::" "-v 123 -t123"  
    getopt "hv:t::" "-v123 -t123"  # -v and 123 doesn't have whitespace
    
    # -h takes no value.
    getopt "hv:t::" "-h -v123"
    
    
    # This is wrong. after -t can't have whitespace.
    # Only optional params cannot have whitespace between key and value
    getopt "hv:t::" "-v 123 -t 123"
    
    # Multiple arguments that takes value.
    getopt "h:v:t::g::" "-h abc -v 123 -t21"
    
    # Multiple arguments without value
    # All of these are correct
    getopt "hvt" "-htv"
    getopt "hvt" "-h -t -v"
    getopt "hvt" "-tv -h"
    

    Here h,v,t are the options and -h -v -t is how options should be given in command-line.

    1. 'h' is a no-value option.
    2. 'v:' implies that option -v has value and is a mandatory option. ':' means has a value.
    3. 't::' implies that option -t has value but is optional. '::' means optional.

    In optional param, value cannot have whitespace separation with the option. So, in "-t123" example, -t is option 123 is value.

    Syntax: Second Form

    getopt [getopt_options] [--] [optstring] [parameters]
    

    Here after getopt is split into five parts

    • The command itself i.e. getopt
    • The getopt_options, it describes how to parse the arguments. single dash long options, double dash options.
    • --, separates out the getopt_options from the options you want to parse and the allowed short options
    • The short options, is taken immediately after -- is found. Just like the Form first syntax.
    • The parameters, these are the options that you have passed into the program. The options you want to parse and get the actual values set on them.

    Examples

    getopt -l "name:,version::,verbose" -- "n:v::V" "--name=Karthik -version=5.2 -verbose"
    

    Syntax: Third Form

    getopt [getopt_options] [-o options] [--] [optstring] [parameters]
    

    Here after getopt is split into five parts

    • The command itself i.e. getopt
    • The getopt_options, it describes how to parse the arguments. single dash long options, double dash options.
    • The short options i.e. -o or --options. Just like the Form first syntax but with option "-o" and before the "--" (double dash).
    • --, separates out the getopt_options from the options you want to parse and the allowed short options
    • The parameters, these are the options that you have passed into the program. The options you want to parse and get the actual values set on them.

    Examples

    getopt -l "name:,version::,verbose" -a -o "n:v::V" -- "-name=Karthik -version=5.2 -verbose"
    

    GETOPT_OPTIONS

    getopt_options changes the way command-line params are parsed.

    Below are some of the getopt_options

    Option: -l or --longoptions

    Means getopt command should allow multi-character options to be recognised. Multiple options are separated by comma.

    For example, --name=Karthik is a long option sent in command line. In getopt, usage of long options are like

    getopt "name:,version" "--name=Karthik"
    

    Since name: is specified, the option should contain a value

    Option: -a or --alternative

    Means getopt command should allow long option to have a single dash '-' rather than double dash '--'.

    Example, instead of --name=Karthik you could use just -name=Karthik

    getopt "name:,version" "-name=Karthik"
    

    A complete script example with the code:

    #!/bin/bash
    
    # filename: commandLine.sh
    # author: @theBuzzyCoder
    
    showHelp() {
    # `cat << EOF` This means that cat should stop reading when EOF is detected
    cat << EOF  
    Usage: ./installer -v <espo-version> [-hrV]
    Install Pre-requisites for EspoCRM with docker in Development mode
    
    -h, -help,          --help                  Display help
    
    -v, -espo-version,  --espo-version          Set and Download specific version of EspoCRM
    
    -r, -rebuild,       --rebuild               Rebuild php vendor directory using composer and compiled css using grunt
    
    -V, -verbose,       --verbose               Run script in verbose mode. Will print out each step of execution.
    
    EOF
    # EOF is found above and hence cat command stops reading. This is equivalent to echo but much neater when printing out.
    }
    
    
    export version=0
    export verbose=0
    export rebuilt=0
    
    # $@ is all command line parameters passed to the script.
    # -o is for short options like -v
    # -l is for long options with double dash like --version
    # the comma separates different long options
    # -a is for long options with single dash like -version
    options=$(getopt -l "help,version:,verbose,rebuild,dryrun" -o "hv:Vrd" -a -- "$@")
    
    # set --:
    # If no arguments follow this option, then the positional parameters are unset. Otherwise, the positional parameters 
    # are set to the arguments, even if some of them begin with a ‘-’.
    eval set -- "$options"
    
    while true
    do
    case $1 in
    -h|--help) 
        showHelp
        exit 0
        ;;
    -v|--version) 
        shift
        export version=$1
        ;;
    -V|--verbose)
        export verbose=1
        set -xv  # Set xtrace and verbose mode.
        ;;
    -r|--rebuild)
        export rebuild=1
        ;;
    --)
        shift
        break;;
    esac
    shift
    done
    

    Running this script file:

    # With short options grouped together and long option
    # With double dash '--version'
    
    bash commandLine.sh --version=1.0 -rV
    # With short options grouped together and long option
    # With single dash '-version'
    
    bash commandLine.sh -version=1.0 -rV
    
    # OR with short option that takes value, value separated by whitespace
    # by key
    
    bash commandLine.sh -v 1.0 -rV
    
    # OR with short option that takes value, value without whitespace
    # separation from key.
    
    bash commandLine.sh -v1.0 -rV
    
    # OR Separating individual short options
    
    bash commandLine.sh -v1.0 -r -V
    
    0 讨论(0)
提交回复
热议问题