How to create a bash script that takes arguments?

后端 未结 5 1538
一整个雨季
一整个雨季 2021-01-31 10:33

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 scr

相关标签:
5条回答
  • 2021-01-31 11:10

    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.

    0 讨论(0)
  • 2021-01-31 11:18

    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 讨论(0)
  • 2021-01-31 11:23

    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?

    0 讨论(0)
  • 2021-01-31 11:24

    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 "${*}"
    
    0 讨论(0)
  • 2021-01-31 11:37

    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.

    0 讨论(0)
提交回复
热议问题