How do I parse command line arguments in Bash?

Say, I have a script that gets called with this line:

./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile

or this one:

./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile 

What's the accepted way of parsing this such that in each case (or some combination of the two) $v , $f , and $d will all be set to true and $outFile will be equal to /fizz/someOtherFile ?


Preferred Method: Using straight bash without getopt[s]

I originally answered the question as the OP asked. This Q/A is getting a lot of attention, so I should also offer the non-magic way to do this. I'm going to expand upon guneysus's answer to fix the nasty sed and include Tobias Kienzler's suggestion.

Two of the most common ways to pass key value pair arguments are:

Straight Bash Space Separated

Usage ./myscript.sh -e conf -s /etc -l /usr/lib /etc/hosts

#!/bin/bash

POSITIONAL=()
while [[ $# -gt 0 ]]
do
key="$1"

case $key in
    -e|--extension)
    EXTENSION="$2"
    shift # past argument
    shift # past value
    ;;
    -s|--searchpath)
    SEARCHPATH="$2"
    shift # past argument
    shift # past value
    ;;
    -l|--lib)
    LIBPATH="$2"
    shift # past argument
    shift # past value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument
    ;;
    *)    # unknown option
    POSITIONAL+=("$1") # save it in an array for later
    shift # past argument
    ;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters

echo FILE EXTENSION  = "${EXTENSION}"
echo SEARCH PATH     = "${SEARCHPATH}"
echo LIBRARY PATH    = "${LIBPATH}"
echo DEFAULT         = "${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 "$1"
fi

Straight Bash Equals Separated

Usage ./myscript.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

#!/bin/bash

for i in "$@"
do
case $i in
    -e=*|--extension=*)
    EXTENSION="${i#*=}"
    shift # past argument=value
    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    shift # past argument=value
    ;;
    -l=*|--lib=*)
    LIBPATH="${i#*=}"
    shift # past argument=value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument with no value
    ;;
    *)
          # unknown option
    ;;
esac
done
echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 $1
fi

To better understand ${i#*=} search for "Substring Removal" in this guide. It is functionally equivalent to `sed 's/[^=]*=//' <<< "$i"` which calls a needless subprocess or `echo "$i" | sed 's/[^=]*=//'` `echo "$i" | sed 's/[^=]*=//'` which calls two needless subprocesses.

Using getopt[s]

from: http://mywiki.wooledge.org/BashFAQ/035#getopts

Never use getopt(1). getopt cannot handle empty arguments strings, or arguments with embedded whitespace. Please forget that it ever existed.

The POSIX shell (and others) offer getopts which is safe to use instead. Here is a simplistic getopts example:

#!/bin/sh

# A POSIX variable
OPTIND=1         # Reset in case getopts has been used previously in the shell.

# Initialize our own variables:
output_file=""
verbose=0

while getopts "h?vf:" opt; do
    case "$opt" in
    h|?)
        show_help
        exit 0
        ;;
    v)  verbose=1
        ;;
    f)  output_file=$OPTARG
        ;;
    esac
done

shift $((OPTIND-1))

[ "${1:-}" = "--" ] && shift

echo "verbose=$verbose, output_file='$output_file', Leftovers: $@"

# End of file

The advantages of getopts are:

  • It's portable, and will work in eg dash.
  • It can handle things like -vf filename in the expected Unix way, automatically.
  • The disadvantage of getopts is that it can only handle short options ( -h , not --help ) without trickery.

    There is a getopts tutorial which explains what all of the syntax and variables mean. In bash, there is also help getopts , which might be informative.


    No answer mentions enhanced getopt. And the top-voted answer is misleading: It ignores -⁠vfd style short options (requested by the OP), options after positional arguments (also requested by the OP) and it ignores parsing-errors. Instead:

  • Use enhanced getopt from util-linux or formerly GNU glibc .1
  • It works with getopt_long() the C function of GNU glibc.
  • Has all useful distinguishing features (the others don't have them):
  • handles spaces, quoting characters and even binary in arguments2
  • it can handle options at the end: script.sh -o outFile file1 file2 -v
  • allows = -style long options: script.sh --outfile=fileOut --infile fileIn
  • Is so old already3 that no GNU system is missing this (eg any Linux has it).
  • You can test for its existence with: getopt --test → return value 4.
  • Other getopt or shell-builtin getopts are of limited use.
  • The following calls

    myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
    myscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFile
    myscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFile
    myscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfd
    myscript ./foo/bar/someFile -df -v --output /fizz/someOtherFile
    

    all return

    verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile
    

    with the following myscript

    #!/bin/bash
    
    getopt --test > /dev/null
    if [[ $? -ne 4 ]]; then
        echo "I’m sorry, `getopt --test` failed in this environment."
        exit 1
    fi
    
    OPTIONS=dfo:v
    LONGOPTIONS=debug,force,output:,verbose
    
    # -temporarily store output to be able to check for errors
    # -e.g. use “--options” parameter by name to activate quoting/enhanced mode
    # -pass arguments only via   -- "$@"   to separate them correctly
    PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTIONS --name "$0" -- "$@")
    if [[ $? -ne 0 ]]; then
        # e.g. $? == 1
        #  then getopt has complained about wrong arguments to stdout
        exit 2
    fi
    # read getopt’s output this way to handle the quoting right:
    eval set -- "$PARSED"
    
    # now enjoy the options in order and nicely split until we see --
    while true; do
        case "$1" in
            -d|--debug)
                d=y
                shift
                ;;
            -f|--force)
                f=y
                shift
                ;;
            -v|--verbose)
                v=y
                shift
                ;;
            -o|--output)
                outFile="$2"
                shift 2
                ;;
            --)
                shift
                break
                ;;
            *)
                echo "Programming error"
                exit 3
                ;;
        esac
    done
    
    # handle non-option arguments
    if [[ $# -ne 1 ]]; then
        echo "$0: A single input file is required."
        exit 4
    fi
    
    echo "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"
    

    1 enhanced getopt is available on most “bash-systems”, including Cygwin; on OS X try brew install gnu-getopt
    2 the POSIX exec() conventions have no reliable way to pass binary NULL in command line arguments; those bytes prematurely end the argument
    3 first version released in 1997 or before (I only tracked it back to 1997)


    getopt() / getopts() is a good option. Stolen from here:

    The simple use of "getopt" is shown in this mini-script:

    #!/bin/bash
    echo "Before getopt"
    for i
    do
      echo $i
    done
    args=`getopt abc:d $*`
    set -- $args
    echo "After getopt"
    for i
    do
      echo "-->$i"
    done
    

    What we have said is that any of -a, -b, -c or -d will be allowed, but that -c is followed by an argument (the "c:" says that).

    If we call this "g" and try it out:

    bash-2.05a$ ./g -abc foo
    Before getopt
    -abc
    foo
    After getopt
    -->-a
    -->-b
    -->-c
    -->foo
    -->--
    

    We start with two arguments, and "getopt" breaks apart the options and puts each in its own argument. It also added "--".

    链接地址: http://www.djcxy.com/p/4124.html

    上一篇: 如何从项目中删除所有.pyc文件?

    下一篇: 如何解析Bash中的命令行参数?