如何创建一个带参数的bash脚本?

时间:2011-01-03 18:18:51

标签: bash

我已经知道了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 个答案:

答案 0 :(得分: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[@]}"

编辑:为了完整性,我提供了一个处理需要参数的选项的例子。

答案 1 :(得分:11)

如果您使用的是getops,请在案例陈述后按$OPTIND-1换班。那么$*中剩下的就是其他一切,这可能是你想要的。

shift $(( ${OPTIND} - 1 )); echo "${*}"

答案 2 :(得分: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等可选参数,但需要installuninstall后跟要安装的内容的名称。基本使用模式是:

jlss install program

但可选模式是:

jlss -d $HOME -u me -g mine -x -p install program

我没有显示所有jlss因为它有大约12个选项 - 它不像rcsunco那么紧凑。


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

  • 你会拿起强制性的论点,将它们移开。
  • 然后用标志处理可选参数。
  • 最后,如果合适,您可以处理额外的“文件名”参数。

如果您正在处理散布有选项参数的强制参数(在强制参数之前和之后),那么您还有更多工作要做。这被许多VCS系统使用; CVS和GIT都有设施:

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

在这里,您运行一个getopts循环来获取全局选项;拿起强制性的论点;并运行第二个getopts循环来获取子选项(并且可能在'文件名'参数上运行最后一个循环)。

生活不是很有趣吗?

答案 3 :(得分:2)

我听到一个完全相反的事情,你不应该使用getopt,而应该getopts内置。

Cross-platform getopt for a shell script

  

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

     

POSIX shell(以及其他)提供可安全使用的getopts   代替。

答案 4 :(得分:1)

这是另一种“Option-ize你的shell脚本”的方法(不使用getopt或getopts):

http://bsdpants.blogspot.com/2007/02/option-ize-your-shell-scripts.html