开发者

How to create a bash script that takes arguments?

I already know about getopts, and this is fine, but it is annoying that you have to have a flag even for mandatory arguments.

Ideally, I'd like to be able to have a script which receives arguments in this form:

script.sh [optional arguments] [anything required]

for example

script.sh -rvx 开发者_如何学Gooutput_file.txt

where the script says you HAVE to have an output file. Is there any easy way to do this?

As far as I know, with getopts it would have to look like: script.sh -rvx -f output_file.txt, and that is just not very clean.

I can also use python if necessary, but only have 2.4 available, which is a bit dated.


Don't use the getopts builtin, use getopt(1) instead. They are (subtly) different and do different things well. For you scenario you could do this:

#!/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[@]}"

EDIT: for completeness I have provided an example of handling an option requiring an argument.


If you are using getops, just shift by $OPTIND-1 after your case statement. Then what is left in $* will be everything else, which is probably what you want.

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


You're are suffering from illusions; using getopts does not require mandatory arguments prefixed by a flag letter. I tried to find a suitable example from my corpus of scripts; this is a semi-decent approximation. It is called rcsunco and is used to cancel a checkout from RCS. I haven't modified it in a while, I see; I use it quite often (because I haven't migrated from RCS completely, yet).

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

It's only a semi-decent approximation; the script quietly does nothing if you don't supply any file names after the optional arguments. However, if you need to, you can check that the mandatory arguments are present after the 'shift'. Another script of mine does have mandatory arguments. It contains:

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

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

So, that command (jlss) can take optional arguments such as -d $HOME, but requires either install or uninstall followed by the name of something to install. The basic mode of use is:

jlss install program

But the optional mode is:

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

I didn't show all of jlss because it has about 12 options - it isn't as compact as rcsunco.


If you were dealing with mandatory arguments before option arguments, then you'd have to do a bit more work:

  • You'd pick up the mandatory arguments, shifting them out of the way.
  • Then you process the optional arguments with the flags.
  • Finally, if appropriate, you handle the extra 'file name' arguments.

If you are dealing with mandatory arguments interspersed with option arguments (both before and after the mandatory ones), then you have still more work to do. This is used by many VCS systems; CVS and GIT both have the facility:

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

Here, you run one getopts loop to get the global options; pick up the mandatory arguments; and run a second getopts loop to get the sub-options (and maybe run a final loop over the 'file name' arguments).

Isn't life fun?


And I heard a completely opposite thing, that you shouldn't use getopt, but the getopts builtin.

Cross-platform getopt for a shell script

Never use getopt(1). getopt cannot handle empty arguments strings, or arguments with embedded whitespace. Please forget that it ever existed.

The POSIX shell (and others) offer getopts which is safe to use instead.


Here's yet another way to "Option-ize your shell scripts" (whithout using getopt or getopts):

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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜