【问题标题】:BASH getopts optional argumentsBASH getopts 可选参数
【发布时间】:2013-06-10 02:02:51
【问题描述】:

我正在尝试创建一个 shell 脚本,它有两个强制参数(arg1、arg2),然后是一个可选的 -b 标志以及用户选择使用该标志时需要遵循的参数。

让我解释一下:

这是一个安装脚本,它利用 GIT 从 Github 存储库中获取应用程序。用户在终端 f.ex. 中输入:

./shellscript.sh new <app_name> # fetches master branch by default

并且脚本从此存储库中获取主分支的一个实例。然而,用户是否应该选择使用可选的 -b 标志,这意味着他/她想要指定要获取的分支,例如开发分支。意味着用户可以这样做:

./shellscript.sh new <app_name> -b develop # or which ever branch there is available

我也很好奇如何让脚本工作,这样用户在“new”参数和“app_name”参数之前输入 -b 标志+branch_name 就无关紧要了。但这也许不是目前最重要的事情。

要知道我到底要构建什么,这里是一个指向我当前脚本的链接,它只接受两个强制参数并且只获取主分支:My Super Cool Artisan Executable Script

P.S.:我一直在尝试使用 getopts 的许多示例,我在 * 和其他博客上都找到了这些示例,但没有一个可以帮助我完全使其工作。因此,我在这里向你们所有伟大的人寻求帮助。

非常感谢,请务必查看我的Linux Mint / Ubuntu - Post Install Script for only cool people(you guys and those switching over to Linux from Windows/Mac)

问候, 绒毛

【问题讨论】:

  • 请插入您已经尝试过的代码。

标签: linux bash shell unix getopts


【解决方案1】:

我通常自己编写以允许短选项和长选项:

function Usage()
{
cat <<-ENDOFMESSAGE

$0 [OPTION] REQ1 REQ2

options:

    -b -branch  branch to use
    -h --help   display this message

ENDOFMESSAGE
    exit 1
}

function Die()
{
    echo "$*"
    exit 1
}

function GetOpts() {
    branch=""
    argv=()
    while [ $# -gt 0 ]
    do
        opt=$1
        shift
        case ${opt} in
            -b|--branch)
                if [ $# -eq 0 -o "${1:0:1}" = "-" ]; then
                    Die "The ${opt} option requires an argument."
                fi
                branch="$1"
                shift
                ;;
            -h|--help)
                Usage;;
            *)
                if [ "${opt:0:1}" = "-" ]; then
                    Die "${opt}: unknown option."
                fi
                argv+=(${opt});;
        esac
    done 
}

GetOpts $*
echo "branch ${branch}"
echo "argv ${argv[@]}"

【讨论】:

  • 我喜欢你的方法。我将对此进行测试。谢谢!
【解决方案2】:

Unix 实用程序通常采用可选参数(“标志”)位置参数之前,尽管大多数 GNU 实用程序,包括 C 库函数 getopt 的 GNU 实现,都对命令行参数进行混洗,以便可选参数首先出现。但是,bash 内置 getopts 不会随机播放,这意味着您可以自行决定是否这样做。

getopts 始终以编号为变量OPTIND 的值的参数开头。 (每次执行 bash 函数时,OPTIND 都设置为 1,并且它是一个全局变量。因此需要注意相互调用的 bash 函数。)如果您愿意,您可以自己设置 OPTIND,然后下一个调用getopts 将从该索引开始。或者,您可以使用shift 来转移所有命令行参数。

因此,例如,您可以这样做:

# "shift" version: always shift consumed arguments
local verb="$1" branch=master app_name option
shift
case $verb in
  new)  app_name="$1"
        shift
        while getopts b: option; do
          case $option in
            b) branch=$OPTARG;;
            *) # handle the error;;
          esac
        done
        shift $((OPTIND - 1));;
  *) # handle the error or other subcommands;;
esac
# At this point, there are still arguments if ((OPTIND > 0))

或者:

# non-shift version: use OPTIND to index arguments
local verb="${!OPTIND}" branch=master app_name option
OPTIND=$((OPTIND + 1))
case $verb in
  new)  app_name="${!OPTIND}"
        OPTIND=$((OPTIND + 1))
        while getopts b: option; do
          case $option in
            b) branch=$OPTARG;;
            *) # handle the error;;
          esac
        done;;
  *) # handle the error or other subcommands;;
esac
# At this point, there are still arguments if ((OPTIND > $#))

【讨论】:

  • 嗨@rici,您的解决方案看起来很有希望。虽然我收到此错误:“本地:只能在函数中使用”。你会怎么做?谢谢你!
  • 把它放在一个函数中或者不要打扰本地声明,你的选择。我喜欢将程序分解为函数,但 YMMV。如果您删除本地声明,请记住初始化变量。
  • +1! ${!OPTIND} 对我来说是新的!恕我直言OPTIND=((OPTIND + 1)) 不正确。 OPTIND=$((OPTIND+1))((OPTIND=OPTIND+1)) 或简单的 ((++OPTIND)) 都可以。
【解决方案3】:

稍微简化一点的版本,使用那个getopts可以报错:

#!/usr/bin/bash

help() { echo -e "Usage\n\t$0: new <app_name> [-b <develop>]" >&2;}
die() { [ -n "$1" ] && echo -e "Error: $1\n" >&2; help; [ -z "$1" ]; exit;}

[ $# -lt 2 ] && die "Too few args"
[ $1 != "new" ] && die "Bad first arg ($1)"
app_name=$2
shift 2

unset develop
while getopts "b:" opt; do
  case $opt in
    \?) exit 1;;
    b) develop="$OPTARG";;
  esac
done

echo "app_name: $app_name, develop: $develop"

测试:

$./test new App
app_name: App, develop:
$./test new App -b Dev
app_name: App, develop: Dev

无论如何,我可能建议使用标准的参数传递方式。你可以使用-n,而不是new

【讨论】: