I want to design a shell script as a wrapper for a couple of scripts. I would like to specify parameters for myshell.sh using getopts and pass the remaining parameters in the same order to the script specified.

If myshell.sh is executed like:

myshell.sh -h hostname -s test.sh -d waittime param1 param2 param3

myshell.sh param1 param2 -h hostname param3 -d waittime -s test.sh

myshell.sh param1 -h hostname -d waittime -s test.sh param2 param3

All of the above should be able to call as

test.sh param1 param2 param3

Is it possible to utilize the options parameters in the myshell.sh and post remaining parameters to underlying script?

  • What are you trying to do ? You want to call test.sh param1 param2 param3 into myshell.sh ? – Luc M Jul 31 '12 at 15:09
  • Sorry if it is not clear from question. Yes. I want to enable my script to handle a mix of positional parameters and getopt values. everything which is remaining from getopt should be passed with underlying shell script. – SiB Jul 31 '12 at 15:12
  • 1
    Only the first line is compliant with the unix standards (see below) for option processing. Doing it otherwise will be a lot more work to get right, and to maintain. – Henk Langeveld Jul 31 '12 at 22:47

Sorry for commenting on an old thread, but thought I'd post for those, like me who were searching on how to do this...

I wanted to do something similar to the OP, and I found the relevant information I required here and here

Essentially if you want to do something like:

script.sh [options] ARG1 ARG2

Then get your options like this:

while getopts "h:u:p:d:" flag; do
case "$flag" in

And then you can get your positional arguments like this:


More information and details are available through the link above.

Hope that helps!!

  • 26
    But this does not allow positional arguments to precede the flags, which was part of OP question. Another standard way to handle positionals /after/ flags is to just do shift $((OPTIND-1)) after your esac and then handle positionals normally. – Chinasaur Jul 23 '13 at 22:41
  • 1
    @Chinasaur When I used ARG1=${@:$OPTIND:1} after while it worked for me for both script.sh -h localhost abc and script.sh abc -h localhost = in ARG1 I had value abc in both cases. Can you be more specific what is not working (what I misunderstood) ? Thanks! – Betlista Jul 14 '17 at 10:28
  • I can not get ARG1. It is empty. – Sigur Oct 3 '17 at 2:05
  • 2
    Another common pattern is to shift off all args processes by getopts and then process the remaning ones with at $1, $2: while getopts "h:u:p:d:" flag; do .... done; shift $(($OPTIND - 1)); echo $1; // Positional arg1 echo $2; // Positional arg2 – Erik Westrup Mar 8 '18 at 9:35
  • @Chinasaur At least in C (man 3 getopt), getopt moves the arguments around: By default, getopt() permutes the contents of argv as it scans, so that eventually all the nonoptions are at the end. I'm guessing man 1 getopts acts the same way, though I don't see anything about it with a quick glance to the man page. – Limited Atonement May 6 '18 at 2:44

Mix opts and args :

echo "options :"
while [ $# -gt 0 ]
    unset OPTIND
    unset OPTARG
    while getopts as:c:  options
    case $options in
            a)  echo "option a  no optarg"
            s)  serveur="$OPTARG"
                    echo "option s = $serveur"
            c)  cible="$OPTARG"
                    echo "option c = $cible"
   shift $((OPTIND-1))
   ARGS="${ARGS} $1 "

echo "ARGS : $ARGS"
exit 1


bash test.sh  -a  arg1 arg2 -s serveur -c cible  arg3
options :
option a  no optarg
option s = serveur
option c = cible
ARGS :  arg1  arg2  arg3
  • For the ARGS variable, this results in a cleaner string that is empty (i.e. no blank spaces) if there are no arguments: ARGS="${ARGS:+${ARGS} }${1}" – lord_nimon Mar 11 at 15:02

getopts won't parse the mix of param1 and -n options.

It is much better to put param1-3 into options like others.

Furthermore you can use already existing libraries such as shflags. It is pretty smart and it is easy to use.

And the last way is to write your own function to parse params without getopts, just iterating all params through case construction. It is the hardest way but it is the only way to match your expectations exactly.

  • Thank you. The way I chose here is parse getopts parameters and then shift and pass parameters to underlying script. – SiB Jul 31 '12 at 15:45

I thought up one way that getopts can be extended to truly mix options and positional parameters. The idea is to alternate between calling getopts and assigning any positional parameters found to n1, n2, n3, etc.:

parse_args() {
    _parse_args 1 "$@"

_parse_args() {
    local n="$1"

    local options_func="$1"

    local OPTIND
    "$options_func" "$@"
    shift $(( OPTIND - 1 ))

    if [ $# -gt 0 ]; then
        eval test -n \${n$n+x}
        if [ $? -eq 0 ]; then
            eval n$n="\$1"

        _parse_args $(( n + 1 )) "$options_func" "$@"

Then in the OP's case, you could use it like:

main() {
    local n1='' n2='' n3=''
    local duration hostname script

    parse_args parse_main_options "$@"

    echo "n1 = $n1"
    echo "n2 = $n2"
    echo "n3 = $n3"
    echo "duration = $duration"
    echo "hostname = $hostname"
    echo "script   = $script"

parse_main_options() {
    while getopts d:h:s: opt; do
        case "$opt" in
            d) duration="$OPTARG" ;;
            h) hostname="$OPTARG" ;;
            s) script="$OPTARG"   ;;

main "$@"

Running it shows the output:

$ myshell.sh param1 param2 -h hostname param3 -d waittime -s test.sh
n1 = param1
n2 = param2
n3 = param3
duration = waittime
hostname = hostname
script   = test.sh

Just a proof of concept, but maybe it's useful to someone.

Note: there's a gotcha if one function that uses parse_args calls another function that uses parse_args and the outer function declares e.g. local n4='', but the inner one doesn't and 4 or more positional parameters are passed to the inner function


Just mashed up a quickie, which easily handles a mixture of options and positional-parameters (leaving only positional-params in $@):

while [ ${#} -gt 0 ];do OPTERR=0;OPTIND=1;getopts "p:o:hvu" arg;case "$arg" in
        p) echo "Path:   [$OPTARG]" ;;
        o) echo "Output: [$OPTARG]" ;;
        h) echo "Help"              ;;
        v) echo "Version"           ;;
    \?) SET+=("$1")                                           ;;
    *) echo "Coding error: '-$arg' is not handled by case">&2 ;;
esac;shift;[ "" != "$OPTARG" ] && shift;done
[ ${#SET[@]} -gt 0 ] && set "" "${SET[@]}" && shift

echo -e "=========\nLeftover (positional) parameters (count=$#) are:"
for i in `seq $#`;do echo -e "\t$i> [${!i}]";done

Sample output:

[root@hots:~]$ ./test.sh 'aa bb' -h -v -u -q 'cc dd' -p 'ee ff' 'gg hh' -o ooo
Coding error: '-u' is not handled by case
Path:   [ee ff]
Output: [ooo]
Leftover (positional) parameters (count=4) are:
        1> [aa bb]
        2> [-q]
        3> [cc dd]
        4> [gg hh]
  • 1
    This does not allow combining several options in a single "-abc" argument. – Raúl Salinas-Monteagudo Oct 27 '15 at 12:52

There are some standards for unix option processing, and in shell programming, getopts is the best way of enforcing them. Almost any modern language (perl, python) has a variant on getopts.

This is just a quick example:

command [ options ] [--] [ words ]
  1. Each option must start with a dash, -, and must consist of a single character.

  2. The GNU project introduced Long Options, starting with two dashes --, followed by a whole word, --long_option. The AST KSH project has a getopts that also supports long options, and long options starting with a single dash, -, as in find(1) .

  3. Options may or may not expect arguments.

  4. Any word not starting with a dash, -, will end option processing.

  5. The string -- must be skipped and will end option processing.

  6. Any remaining arguments are left as positional parameters.

The Open Group has a section on Utility Argument Syntax

Eric Raymond's The Art of Unix Programming has a chapter on traditional unix choices for option letters and their meaning.


You can try this trick: after while loop with optargs, just use this snippet

#shift away all the options so that only positional agruments
#remain in $@

for (( i=0; i<OPTIND-1; i++)); do


However, this approach has a bug:

    all the options after the first positional argument are ingored by getopts and are considered as positional arguments - event those that are correct (see sample output: -m and -c are among positional arguments)

Maybe it has even more bugs...

Look at the whole example:

while getopts :abc opt; do
    case $opt in
        echo found: -a
        echo found: -b
        echo found: -c
        \?) echo found bad option: -$OPTARG

#OPTIND-1 now points to the first arguments not beginning with -

#shift away all the options so that only positional agruments
#remain in $@



echo "positional: $POSITIONAL"


[root@host ~]# ./abc.sh -abc -de -fgh -bca haha blabla -m -c
found: -a
found: -b
found: -c
found bad option: -d
found bad option: -e
found bad option: -f
found bad option: -g
found bad option: -h
found: -b
found: -c
found: -a
positional: haha blabla -m -c

Your Answer

