0

I am re-learning getopt with a tiny script in bash, but the second parameters fall into default branch of case.

#! /bin/bash

LONG_OPTION_LIST=(
    "arg-a"
    "arg-b:"
    "arg-c:"
)
SORT_OPTION_LIST=(
    "a"
    "b:"
    "c:"
)
# Read the parameters
opts=$(getopt -q \
  --longoptions "$(printf "%s," "${LONG_OPTION_LIST[@]}")" \
  --name "$(basename "$0")" \
  --options "$(printf "%s" "${SORT_OPTION_LIST[@]}")" \
  -- "$@"
)
eval set -- "$opts"

echo "##$1##"
echo "##$2##"
echo "##$3##"
echo "##$4##"
echo "##$5##"
echo "#########"

argA=0
# It it is same a queue (process the head) because $1 and $2
for arg
do
    echo $1
    echo $2
    echo "--------"
    case "$arg" in
        --arg-a | -a)
            argA=1
            shift 1
            ;;
        --arg-b | -b)
            argB=$2
            shift 2
            ;;
        --arg-c | -c)
            argC=$2
            shift 2
            ;;
        *)
            echo "###$1###"
            echo "break"
            echo "_________"
            break
            ;;
    esac
done

echo "argA $argA"
echo "argB $argB"
echo "argC $argC"

And some examples:

user@pc:/tmp$ ./test.bash -a
##-a##
##--##
####
####
####
#########
-a
--
--------
--

--------
###--###
break
_________
argA 1
argB 
argC 
user@pc:/tmp$ ./test.bash -b 111
##-b##
##111##
##--##
####
####
#########
-b
111
--------
--

--------
###--###
break
_________
argA 0
argB 111
argC 
user@pc:/tmp$ ./test.bash -a -b 111
##-a##
##-b##
##111##
##--##
####
#########
-a
-b
--------
-b
111
--------
--

--------
###--###
break
_________
argA 1
argB 111
argC 
user@pc:/tmp$ ./test.bash -b 111 -a
##-b##
##111##
##-a##
##--##
####
#########
-b
111
--------
-a
--
--------
###-a###
break
_________
argA 0
argB 111
argC 
tres.14159
  • 234
  • 1
  • 2
  • 6

3 Answers3

1
for arg
do
    ...
    shift 1

I don't think the shift here works like you'd like it to, the words the loop will loop over will be set when the loop starts, and a shift inside it doesn't affect that. E.g.

$ set -- aa bb cc; 
$ for x; do echo $x; shift; done
aa
bb
cc

"$@" will be left empty after the loop, though, since it was shifted once for each element.


Most of the examples using getopt use a while true loop instead, look into $1 and $2, shift manually and end the loop when seeing the -- terminator (which the util-linux getopt always adds, but you could check manually for non-options too):

See e.g.

ilkkachu
  • 133,243
  • 15
  • 236
  • 397
0

You do not need to do a shift for the value. It is done automatically already. The shift 2 actually hides the next option.

Standard way to write a case is like this:

for arg
do
    echo $1
    echo $2
    echo "--------"
    case "$arg" in
        --arg-a | -a)
            argA=1
            ;;
        --arg-b | -b)
            argB=$2
            ;;
        --arg-c | -c)
            argC=$2
            ;;
    esac
    shift
done

Notice the single shift after the case.

Also, you should not put * in cases like that - it will react to any option. If you need a custom reaction to an unexpected option it is better to do something like:

opts=$(getopt ... )
[ $? -eq 0 ] || { 
    echo "Known options are..."
    exit 1
}
White Owl
  • 4,511
  • 1
  • 4
  • 15
  • I don't think you can sensibly use `$1` and `$2` inside `for arg; do` like that, they'll just have whatever values they had before the loop. E.g. `set -- aa bb cc; for x; do echo $1; done` gives `aa` three times. Not that `shift` works either, `set -- aa bb cc; for x; do echo $x; shift; done` gives `aa`, `bb` and `cc`, without the shift affecting the loop. – ilkkachu Mar 21 '22 at 13:52
0

As say @ikkachu the bug is using for instead while, thanks.

And this is the tiny script that it working with getopt:

#! /bin/bash

#~ get_opt.example.sh
#~ Copyright (C) 2022 Miguel de Dios Matias

#~ This program is free software: you can redistribute it and/or modify
#~ it under the terms of the GNU General Public License as published by
#~ the Free Software Foundation, either version 3 of the License, or
#~ (at your option) any later version.

#~ This program is distributed in the hope that it will be useful,
#~ but WITHOUT ANY WARRANTY; without even the implied warranty of
#~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#~ GNU General Public License for more details.

#~ You should have received a copy of the GNU General Public License
#~ along with this program. If not, see <http://www.gnu.org/licenses/>.

: '
Examples the calls:

$ ./getopt.example.bash --arg-b 111 -a 2222 3333
argA 1
argB 111
argC 
argD 0
unamedOptions 2222 3333

'

function help() {
    echo "$0 [(--arg-a | -a)] [(--arg-b | -b) <data_b>] [(--arg-c | -c <data_c>)] [-d] [(--help | -h)]"
}

LONG_OPTION_LIST=(
    "arg-a"
    "arg-b:"
    "arg-c:"
    "help"
)
SORT_OPTION_LIST=(
    "a"
    "b:"
    "c:"
    "d"
    "h"
)
# Read the parameters
opts=$(getopt -q \
  --longoptions "$(printf "%s," "${LONG_OPTION_LIST[@]}")" \
  --name "$(basename "$0")" \
  --options "$(printf "%s" "${SORT_OPTION_LIST[@]}")" \
  -- "$@"
)
eval set -- "$opts"

argA=0
argD=0
unamedOptions=()
# It it is same a queue (process the head) because $1 and $2
while true
do
    case "$1" in
        --arg-a | -a)
            argA=1
            ;;
        --arg-b | -b)
            argB=$2
            shift 1
            ;;
        --arg-c | -c)
            argC=$2
            shift 1
            ;;
        -d)
            argD=1
            ;;
        --help | -h)
            help
            exit 0
            ;;
        --)
            # End options now the unamed options
            ;;
        *)
            unamedOptions+=("$1")
            ;;
    esac
    shift 1
    if [ $# -eq 0 ]
    then
        break
    fi
done

echo "argA $argA"
echo "argB $argB"
echo "argC $argC"
echo "argD $argD"
echo "unamedOptions ${unamedOptions[@]}"
tres.14159
  • 234
  • 1
  • 2
  • 6