协慌网

登录 贡献 社区

如何在 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

答案

方法#1:使用不带 getopt 的 bash [s]

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

Bash Space-Separated(例如, - --option argument )(不带 getopt [s])

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

Bash Equals-Separated(例如, - --option=argument )(不带 getopt [s])

用法./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#*=} ,请在本指南中搜索 “Substring Removal”。它在功能上等同于`sed 's/[^=]*=//' <<< "$i"`它调用一个不必要的子进程或`echo "$i" | sed 's/[^=]*=//'`它调用两个不必要的子进程。

方法#2:使用带有 getopt 的 bash [s]

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

getopt(1)限制 (较旧的,相对较新的getopt版本):

  • 无法处理空字符串的参数
  • 无法处理嵌入空格的参数

最近的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的优点是:

  1. 它更便携,并且可以在其他外壳中使用,例如dash
  2. 它可以自动地以典型的 Unix 方式处理多个单选项,如-vf filename

getopts的缺点是它只能处理短选项( -h ,而不是--help )而无需额外的代码。

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

没有回答提到增强的 getopt 。而最高投票的答案是误导性的:它忽略了-⁠vfd样式的短选项(由 OP 请求),位置参数后的选项(也是 OP 请求的)并忽略了解析错误。代替:

  • 使用 util-linux 或以前的 GNU glibc 增强的getopt1
  • 它与getopt_long()使用 GNU glibc 的 C 函数。
  • 所有有用的区别特征(其他没有它们):
    • 处理空格,在参数中引用字符甚至二进制2
    • 它可以在最后处理选项: script.sh -o outFile file1 file2 -v
    • script.sh --outfile=fileOut --infile fileIn = -style long 选项: script.sh --outfile=fileOut --infile fileIn
  • 太旧了已经3没有 GNU 系统缺少这个(例如,任何 Linux 有它)。
  • 你可以用以下方法测试它的存在: getopt --test →返回值 4。
  • 其他getopt或 shell-builtin 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
# saner programming env: these switches turn some bugs into errors
set -o errexit -o pipefail -o noclobber -o nounset

! getopt --test > /dev/null 
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
    echo "I’m sorry, `getopt --test` failed in this environment."
    exit 1
fi

OPTIONS=dfo:v
LONGOPTS=debug,force,output:,verbose

# -use ! and PIPESTATUS to get exit code with errexit set
# -temporarily store output to be able to check for errors
# -activate quoting/enhanced mode (e.g. by writing out “--options”)
# -pass arguments only via   -- "$@"   to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
    # e.g. return value is 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"

d=n f=n v=n outFile=-
# 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-getoptsudo port install getopt
2 POSIX exec()约定没有可靠的方法在命令行参数中传递二进制 NULL; 那些字节过早地结束了论证
3第一个版本发布于 1997 年或之前(我只跟踪它回到 1997 年)

来自: digitalpeer.com稍作修改

用法myscript.sh -p=my_prefix -s=dirname -l=libname

#!/bin/bash
for i in "$@"
do
case $i in
    -p=*|--prefix=*)
    PREFIX="${i#*=}"

    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    ;;
    -l=*|--lib=*)
    DIR="${i#*=}"
    ;;
    --default)
    DEFAULT=YES
    ;;
    *)
            # unknown option
    ;;
esac
done
echo PREFIX = ${PREFIX}
echo SEARCH PATH = ${SEARCHPATH}
echo DIRS = ${DIR}
echo DEFAULT = ${DEFAULT}

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