简体   繁体   中英

Multiple calls to getopts in bash

When this script is called

test.sh -a 1 -b 2 -c 3

output is : -a 1 -b 2 1

I was expecting parseRemainingArgs to parse and print all arguments. As you can see all the arguments are passed properly to parseRemainingArgs as seen in first line of output, but it parses only the first argument.

I set the OPTIND as well but that also didn't help.

Need help in identifying what is wrong with this and how to fix that.

Tried all available answers which google provides but none of them solves this problem.

#!/bin/sh
parseRemainingArgs()
{
    echo $@
    OPTIND=1
    while getopts :ha:b:c: o;
    do
        case "$o" in
                h) echo "Print help";;
                a) FIRST_ARG="$OPTARG";;
                b) SECOND_ARG="$OPTARG";;
                c) THIRD_ARG="$OPTARG";;
                \?) ;;
        esac
    done
    echo $FIRST_ARG $SECOND_ARG $THIRD_ARG
}

parseArgs()
{
    AllArgs=$@
    while getopts :ht: o;
    do
        case "$o" in
                t) TARGET="$OPTARG";;
                h) HELP="yes";;
                \?) ;;
        esac
    done
    shift $(($OPTIND - 1))
    parseRemainingArgs $AllArgs
}

parseArgs $@

Revised example command line

Chhaba observed :

To get the problem can you please test your script with the following arguments

 gomod.sh -ta -a 1 -b 2 -c 3 

In this case -t will match in parseArgs but parseRemainingArgs will fail to parse the remaining args. It works great when -t or -h is not given which matches in parseArgs .

OK; that's what I'd expect from the script I used (see the script below in the "'Cannot reproduce' response" section — it is a mild extension of what's in the question). From bash , I get:

$ bash gomod.sh -t a -a 1 -b 2 -c 3
Initial arguments: -t a -a 1 -b 2 -c 3
Doing nothing
OPTIND = 4
Residual arguments: 1 -b 2 -c 3
Parsing Remaining: -t a -a 1 -b 2 -c 3
Ignoring error
a1: a2: a3
$

It's a consequence of you setting AllArgs at the start of parseArgs . It doesn't change as you parse the arguments in "$@" in parseArgs , so you pass the original argument list to parseRemainingArgs , as shown in the diagnostic output. In parseRemainingArgs , -t is not an option, so the loop returns an error on the -t (hence Ignoring error ), and then exits because a is not an option.

You can get more output from:

$ bash gomod.sh -t -a 1 -b 2 -c 3
Initial arguments: -t -a 1 -b 2 -c 3
OPTIND = 3
Residual arguments: 1 -b 2 -c 3
Parsing Remaining: -t -a 1 -b 2 -c 3
Ignoring error
a1: 1 a2: 2 a3 3
$

It ignores the -t error in parseRemainingArgs and then processes each of -a , -b and -c with the corresponding argument.

Alternatively, you could modify the script, replacing the invocation of parseRemainingArgs with:

parseRemainingArgs "$@"

You can then run it like this, for example, and get the output shown:

$ bash gomod.sh -t antelope -- -a 1 -b 22 -c 333
Initial arguments: -t antelope -- -a 1 -b 22 -c 333
OPTIND = 4
Residual arguments: -a 1 -b 22 -c 333
Parsing Remaining: -a 1 -b 22 -c 333
a1: 1 a2: 22 a3 333
$

Note that replacing the -- with nothing, or with abelone (or any other non-option) does not work as you'd want.

You could still add a lot more debugging to make it easier to understand, or run it under bash -x .

'Cannot reproduce' response

I'm not sure what your problem is any more. I've run a slightly modified version of your script — more diagnostics and using back-quotes and expr to calculate the shift (because some of the shells I tested with don't support the $((…)) arithmetic notation). The code was:

#!/bin/sh
parseRemainingArgs()
{
    echo "Parsing Remaining:" "$@"
    OPTIND=1
    while getopts :ha:b:c: o
    do
        case "$o" in
                h) echo "Print help";;
                a) FIRST_ARG="$OPTARG";;
                b) SECOND_ARG="$OPTARG";;
                c) THIRD_ARG="$OPTARG";;
                \?) echo "Ignoring error";;
        esac
    done
    echo a1: $FIRST_ARG a2: $SECOND_ARG a3 $THIRD_ARG
}

parseArgs()
{
    AllArgs=$@
    echo "Initial arguments:" "$@"
    while getopts :ht: o
    do
        case "$o" in
                t) TARGET="$OPTARG";;
                h) HELP="yes";;
                \?) echo "Doing nothing";;
        esac
    done
    #shift $(($OPTIND - 1))
    echo "OPTIND = $OPTIND"
    shift `expr $OPTIND - 1`
    echo "Residual arguments:" "$@"
    parseRemainingArgs $AllArgs
}

parseArgs "$@"

The shells tested were:

  • sh — a link to bash
  • bash
  • dash
  • ksh — Korn shell
  • zsh
  • hsh — Heirloom shell (close to Bourne shell)
  • svr4-sh — Unix System V Release 4 (slightly souped up Bourne shell)

Of these, hsh and svr4-sh did not accept shift $(($OPTIND - 1)) . I'm slightly surprised the Heirloom shell recognizes getopts at all.

The script was called gomod.sh , and the output I got was:

$ for shell in sh bash dash ksh zsh hsh svr4-sh
> do
>     boxecho $shell
>     $shell gomod.sh -a 1 -b 2 -c 3
>     echo
> done
********
** sh **
********
Initial arguments: -a 1 -b 2 -c 3
Doing nothing
OPTIND = 2
Residual arguments: 1 -b 2 -c 3
Parsing Remaining: -a 1 -b 2 -c 3
a1: 1 a2: 2 a3 3

**********
** bash **
**********
Initial arguments: -a 1 -b 2 -c 3
Doing nothing
OPTIND = 2
Residual arguments: 1 -b 2 -c 3
Parsing Remaining: -a 1 -b 2 -c 3
a1: 1 a2: 2 a3 3

**********
** dash **
**********
Initial arguments: -a 1 -b 2 -c 3
Doing nothing
OPTIND = 2
Residual arguments: 1 -b 2 -c 3
Parsing Remaining: -a 1 -b 2 -c 3
a1: 1 a2: 2 a3 3

*********
** ksh **
*********
Initial arguments: -a 1 -b 2 -c 3
Doing nothing
OPTIND = 2
Residual arguments: 1 -b 2 -c 3
Parsing Remaining: -a 1 -b 2 -c 3
a1: 1 a2: 2 a3 3

*********
** zsh **
*********
Initial arguments: -a 1 -b 2 -c 3
Doing nothing
OPTIND = 2
Residual arguments: 1 -b 2 -c 3
Parsing Remaining: -a 1 -b 2 -c 3
a1:  1 -b 2 -c 3 a2: a3

*********
** hsh **
*********
Initial arguments: -a 1 -b 2 -c 3
Doing nothing
OPTIND = 2
Residual arguments: 1 -b 2 -c 3
Parsing Remaining: -a 1 -b 2 -c 3
a1: 1 a2: 2 a3 3

*************
** svr4-sh **
*************
Initial arguments: -a 1 -b 2 -c 3
Doing nothing
OPTIND = 2
Residual arguments: 1 -b 2 -c 3
Parsing Remaining: -a 1 -b 2 -c 3
a1: 1 a2: 2 a3 3

$

boxecho is a trivial script that echoes its arguments inside a box of stars:

echo "** $@ **" | sed -e h -e 's/./*/g' -e p -e x -e p -e x

I hadn't modified it since 1998; I don't often have a subdirectory s with a subdirectory inside that contains a file g , so the absence of quotes around the s/./*/g hadn't ever bit me, but I've fixed the script above (and in my bin directory). There are other ways of writing the sed too; this does, however, work.

It is worth noting that the use of getopts in the parseRemainingArgs function works with the full set of arguments originally passed to the command, despite the implication that it is working on 'the remaining arguments'. You'd pass "$@" to the function if it were to process the as yet unprocessed arguments. It would also run into 'end of arguments' immediately unless you have a GNU getopts functionality without POSIXLY_CORRECT set in the environment since the first such argument would be 1 which is not an option and would terminate the getopts processing.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM