如何创建一个接收参数的Bash脚本?

19

我已经知道getopts的用法,尽管也可以,但是即使是必需参数,你仍然需要有一个标志。

理想情况下,我希望能够编写这样的脚本,以此形式接收参数:

script.sh [optional arguments] [anything required]
例如。
script.sh -rvx output_file.txt

脚本中指定了必须要有输出文件,有没有简便的方法实现?

据我所知,使用getopts需要像这样:script.sh -rvx -f output_file.txt,但这样不太清晰。

如果需要的话,也可以使用Python,但只有2.4版本可用,有些过时。

5个回答

29

不要使用getopts内置命令,而应该使用getopt(1)。它们(微妙地)不同,并且各自擅长不同的事情。对于您的场景,您可以这样做:

#!/bin/bash

eval set -- $(getopt -n $0 -o "-rvxl:" -- "$@")

declare r v x l
declare -a files
while [ $# -gt 0 ] ; do
        case "$1" in
                -r) r=1 ; shift ;;
                -v) v=1 ; shift ;;
                -x) x=1 ; shift ;;
                -l) shift ; l="$1" ; shift ;;
                --) shift ;;
                -*) echo "bad option '$1'" ; exit 1 ;;
                *) files=("${files[@]}" "$1") ; shift ;;
         esac
done

if [ ${#files} -eq 0 ] ; then
        echo output file required
        exit 1
fi

[ ! -z "$r" ] && echo "r on"
[ ! -z "$v" ] && echo "v on"
[ ! -z "$x" ] && echo "x on"

[ ! -z "$l" ] && echo "l == $l"

echo "output file(s): ${files[@]}"

编辑:为了完整起见,我提供了处理需要参数的选项的示例。


这看起来正是我想要的。不过有一个问题:如果我想允许一个带参数的标志(例如,-l file.txt),语法应该是什么?再次感谢。 - A Question Asker
请参考getopt命令的手册。简单说就是,在选项后面添加冒号(例如"-rv:x",表示v需要带一个参数)。getopt还支持长选项,这是它优于getopts内置命令的一种方式。请注意,你需要自己处理额外的非选项参数。 - sorpigal
3
注意,某些版本的 getopt 存在问题。根据 man 手册中的描述:"传统的 getopt(1) 实现无法处理参数和非选项参数中的空格和其他(特定于 shell 的)特殊字符。" 查看 man 手册 以获取更多详细信息并测试您的版本是否存在这些问题。 - Dennis Williamson

11
如果你正在使用getops,只需在case语句后移位$OPTIND-1。然后,剩下的$*将是其他所有内容,这可能是你想要的。
shift $(( ${OPTIND} - 1 )); echo "${*}"

没错。只需检查$#以确保传递了正确数量的必需参数,然后像通常一样访问它们,如$1、$2等。 - Gordon Davisson

4
你存在幻觉;使用 getopts 不需要在标志字母前加上强制性参数。我试图从我的脚本语料库中找到一个合适的例子; 这是一个半合理的近似值。它被称为 rcsunco,用于取消从 RCS 中的检出。我好像有一段时间没有修改它了,但我经常使用它(因为我还没有完全迁移到 RCS)。
#!/bin/sh
#
#   "@(#)$Id: rcsunco.sh,v 2.1 2002/08/03 07:41:00 jleffler Exp $"
#
#   Cancel RCS checkout

# -V print version number
# -n do not remove or rename checked out file (like SCCS unget) (default)
# -r remove checked out file (default)
# -k keep checked out file as $file.keep
# -g checkout (unlocked) file after clean-up
# -q quiet checkout

: ${RCS:=rcs}
: ${CO:=co}

remove=yes
keep=no
get=no
quiet=

while getopts gknqrV opt
do
    case $opt in
    V)  echo "`basename $0 .sh`: RCSUNCO Version $Revision: 2.1 $ ($Date: 2002/08/03 07:41:00 $)" |
        rcsmunger
        exit 0;;
    g)  get=yes;;
    k)  keep=yes;;
    n)  remove=no;;
    q)  quiet=-q;;
    r)  remove=yes;;
    *)  echo "Usage: `basename $0 .sh` [-{n|g}][-{r|k}] file [...]" 1>&2
        exit 1;;
    esac
done

shift $(($OPTIND-1))

for file in $*
do
    rfile=$(rfile $file)
    xfile=$(xfile $rfile)
    if $RCS -u $rfile
    then
        if [ $keep = yes ]
        then
            if [ -f $xfile ]
            then
                mv $xfile $xfile.keep
                echo "$xfile saved in $xfile.keep"
            fi
        elif [ $remove = yes ]
        then rm -f $xfile
        fi
        if [ $get = yes ] && [ $remove = yes -o $keep = yes ]
        then $CO $quiet $rfile
        fi
    fi
done

这只是一个半靠谱的近似值;如果你在可选参数后没有提供任何文件名,脚本会安静地不做任何事情。然而,如果需要的话,你可以在 "shift" 后检查是否存在必需的参数。我的另一个脚本确实有必需的参数。它包含以下内容:

...
shift $(($OPTIND - 1))

case $# in
2)  case $1 in
    install)    MODE=Installation;;
    uninstall)  MODE=Uninstallation;;
    *)          usage;;
    esac;;
*)  usage;;
esac

因此,该命令(jlss)可以接受可选参数,例如-d $HOME,但需要安装或卸载后跟要安装的内容的名称。基本使用模式如下:

jlss install program

但是可选的模式是:
jlss -d $HOME -u me -g mine -x -p install program

我没有展示所有的jlss,因为它有大约12个选项-它不像rcsunco那样紧凑。


如果你在处理强制参数之前处理可选参数,则需要做更多的工作:

  • 你需要获取强制参数并将其移到一边。
  • 然后你使用标志处理可选参数。
  • 最后,如果适当的话,处理额外的“文件名”参数。

如果你正在处理强制参数与可选参数交替出现(在强制参数之前和之后),那么你需要做更多的工作。这被许多版本控制系统所使用;CVS和GIT都有此功能:

git -global option command [-sub option] [...]

在这里,你需要运行一个getopts循环来获取全局选项;获得必填参数;然后再运行第二个getopts循环以获取子选项(可能还需要对“文件名”参数进行最终循环)。

生活不是很有趣吗?


2
我听到了完全相反的说法,即不应该使用getopt,而是使用getopts内置函数。 跨平台shell脚本的getopt

永远不要使用 getopt(1)。 getopt 无法处理空参数字符串或带有嵌入式空格的参数。请忘记它曾经存在过。

POSIX shell(和其他一些shell)提供了 getopts,这个工具可以安全地使用。


1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接