9

I'm trying to create a script which will have a flag with optional options. With getopts it's possible to specify a mandatory argument (using a colon) after the flag, but I want to keep it optional.

It will be something like this:

./install.sh -a 3

or

./install.sh -a3

where 'a' is the flag and '3' is the optional parameter that follows a.

Thanks in advance.

5 Answers 5

9

The getopt external program allows options to have a single optional argument by adding a double-colon to the option name.

# Based on a longer example in getopt-parse.bash, included with
# getopt
TEMP=$(getopt -o a:: -- "$@")
eval set -- "$TEMP"
while true ; do
   case "$1" in
     -a)
        case "$2" in 
          "") echo "Option a, no argument"; shift 2 ;;
          *) echo "Option a, argument $2"; shift 2;;
        esac ;;
     --) shift; break ;;
     *) echo "Internal error!"; exit 1 ;;
   esac
done
Sign up to request clarification or add additional context in comments.

3 Comments

Executing: ./install.sh -a 3 gives "option a, no argment" but the argument is "3"
getopt does not seem to allow a space between an option and its optional argument.
From man getopt: "If the option has an optional argument, it must be written directly after the option character if present."
2

The following is without getopt and it takes an optional argument with the -a flag:

for WORD; do
        case $WORD in
            -a?)  echo "single arg Option"
                SEP=${WORD:2:1}
                echo $SEP
                shift ;;
            -a) echo "split arg Option"
                if [[ ${2:0:1} != "-" && ${2:0:1} != ""]] ; then
                 SEP=$2
                 shift 2
                 echo "arg present"
                 echo $SEP
                else
                 echo "optional arg omitted"
                fi ;;
            -a*) echo "arg Option"
                SEP=${WORD:2}
                echo $SEP
                shift ;;
            -*) echo "Unrecognized Short Option"
                echo "Unrecognized argument"
            ;;
        esac
done

Other options/flags also can be added easily.

2 Comments

Indeed, outer case was not required. edited that. thanks @l0b0
Nit: in "$@" is not required. for WORD; do will loop over the arguments.
-1

Use the getopt feature. On most systems, man getopt will yield documentation for it, and even examples of using it in a script. From the man page on my system:

The following code fragment shows how one might process the arguments for a command that can take the options -a and -b, and the option -o, which requires an argument.

       args=`getopt abo: $*`
       # you should not use `getopt abo: "$@"` since that would parse
       # the arguments differently from what the set command below does.
       if [ $? != 0 ]
       then
               echo 'Usage: ...'
               exit 2
       fi
       set -- $args
       # You cannot use the set command with a backquoted getopt directly,
       # since the exit code from getopt would be shadowed by those of set,
       # which is zero by definition.
       for i
       do
               case "$i"
               in
                       -a|-b)
                               echo flag $i set; sflags="${i#-}$sflags";
                               shift;;
                       -o)
                               echo oarg is "'"$2"'"; oarg="$2"; shift;
                               shift;;
                       --)
                               shift; break;;
               esac
       done
       echo single-char flags: "'"$sflags"'"
       echo oarg is "'"$oarg"'"

This code will accept any of the following as equivalent:

       cmd -aoarg file file
       cmd -a -o arg file file
       cmd -oarg -a file file
       cmd -a -oarg -- file file

4 Comments

getopt is the right answer, but your long example doesn't demonstrate how to implement an option whose argument is also optional.
This will break if your arguments contain whitespace. Example solution.
Getting down-voted because the content of a standard man page isn't to your liking. Good times on SO.
Or because it doesn't answer OP and contains bugs.
-1

In bash there is some implicit variable:

    $#: contains number of arguments for a called script/function

    $0: contains names of script/function
    $1: contains first argument
    $2: contains second argument
    ...
    $n: contains n-th argument

For example:

    #!/bin/ksh

    if [ $# -ne 2 ]
    then
        echo "Wrong number of argument - expected 2 : $#"
    else
        echo "Argument list:"
        echo "\t$0"
        echo "\t$1"
        echo "\t$2"
    fi

5 Comments

The question asks how to define an option whose argument is also optional.
yeah, and i prefer explain to him how to catch an argument than give him a full answer. Sometime its better for someone to find the way by himself... Btw your answe deserve a -1 because of your "while true"...
@binogure while true is not wrong in and of itself -- there needs to be a terminating condition, but that can be a break.
I initially left out the terminating break while trying to simplify the example that ships with getopt.
I guess you meant #!/bin/bash ;)
-1

My solution:

#!/bin/bash
count=0
skip=0
flag="no flag"
list=($@) #put args in array
for arg in $@ ; do #iterate over array
    count=$(($count+1)) #update counter
    if [ $skip -eq 1 ]; then #check if we have to skip this args
        skip=0
        continue
    fi
    opt=${arg:0:2} #get only first 2 chars as option
    if [ $opt == "-a" ]; then #check if option equals "-a"
       if [ $opt == $arg ] ; then #check if this is only the option or has a flag
            if [ ${list[$count]:0:1} != "-" ]; then #check if next arg is an option
                skip=1 #skip next arg
                flag=${list[$count]} #use next arg as flag
            fi
       else
            flag=${arg:2} #use chars after "-a" as flag
       fi 
    fi
done

echo $flag

3 Comments

in $@ is superfluous. Also, POSIX sh doesn't have arrays. == is not portable.
@l0b0 why is in $@ superfluous? It could be substituded by $list but is needed. I agree the code is not portable and I changed the header to #!/bin/bash
Because for arg will iterate over "$@" by default.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.