如何解析Bash中的命令行参数?

说,我有一个脚本,被调用这一行:

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

或这一个:

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

什么是可接受的解析方式,以便在每种情况下(或两者的某种组合) $v$f$d都将设置为true$outFile将等于/fizz/someOtherFile


首选方法:使用不带getopt的直接bash [s]

我原本是在OP问的时候回答这个问题的。 这个Q / A得到了很多关注,所以我也应该提供非魔术方式来做到这一点。 我打算扩展guneysus的答案来解决讨厌的sed并且包括Tobias Kienzler的建议。

传递键值对参数的两种最常用的方法是:

直击Bash空间分离

用法./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

直击是等分的

用法./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

为了更好地理解本指南中的${i#*=}搜索“子串删除”。 它在功能上等同于`sed 's/[^=]*=//' <<< "$i"` ,它调用一个不必要的子进程或`echo "$i" | sed 's/[^=]*=//'` `echo "$i" | sed 's/[^=]*=//'`它调用两个不必要的子进程。

使用getopt [s]

来自:http://mywiki.wooledge.org/BashFAQ/035#getopts

切勿使用getopt(1)。 getopt不能处理空的参数字符串或嵌入空白的参数。 请忘记它曾经存在过。

POSIX shell(和其他)提供了可以安全使用的getopts 。 以下是一个简单的getopts示例:

#!/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

getopts的优点是:

  • 它是可移植的,并且可以在例如短跑中工作。
  • 它可以自动处理预期的Unix方式的-vf filename
  • getopts的缺点是它只能处理简短的选项( -h ,而不是--help )而不会产生欺骗。

    有一个getopts教程,它解释了所有的语法和变量的含义。 在bash中,也有help getopts ,这可能是信息。


    没有回答提及增强的getopt。 -⁠vfd 答案是误导性的:它忽略-⁠vfd风格的短期选项(由OP请求),位置参数之后的选项(OP也请求),它忽略解析错误。 代替:

  • 从util-linux或以前的GNU glibc.1使用增强的getopt
  • 它适用于getopt_long() GNU glibc的C函数。
  • 具有所有有用的区别特征(其他人没有):
  • 处理空格,引用字符,甚至在arguments2中的二进制
  • 它可以在最后处理选项: script.sh -o outFile file1 file2 -v
  • 允许= -style长选项: script.sh --outfile=fileOut --infile fileIn
  • 已经很老了,以至于没有GNU系统缺少这个(例如任何Linux都有)。
  • 您可以使用以下方式测试它的存在: getopt --test →返回值4。
  • 其他getopt或shell- getopts的使用受到限制。
  • 以下电话

    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
    

    所有回报

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

    用下面的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"
    

    大多数“bash系统”都提供了1个增强的getopt,包括Cygwin; 在OS X上尝试brew install gnu-getopt
    2 POSIX exec()约定没有可靠的方法在命令行参数中传递二进制NULL; 那些字节过早地结束了参数
    3 1997年或之前发布的第一个版本(我只追溯到1997年)


    getopt() / getopts()是一个不错的选择。 从这里偷来的:

    在这个小脚本中显示了“getopt”的简单使用:

    #!/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
    

    我们所说的是,任何-a,-b,-c或-d都是允许的,但是-c后面跟着一个参数(“c:”表示这个)。

    如果我们称之为“g”并尝试一下:

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

    我们从两个参数开始,“getopt”将选项分开,并将每个选项放在它自己的参数中。 它还添加了“ - ”。

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

    上一篇: How do I parse command line arguments in Bash?

    下一篇: When to wrap quotes around a shell variable?