3

I have copied code from tutorialspoint's getopt article and got the following script to work (sort of):


##argument_script.sh
VARS=`getopt -o i::o:: --long input::,output:: -- "$@"`
eval set -- "$VARS"

# extract options and their arguments into variables.
while true ; do
    case "$1" in

        -i|--input)
            case "$2" in
                "") MAPPE='/default/input/here/' ; shift 2 ;;
                *) MAPPE=$2 ; shift 2 ;;
            esac ;;
        
            -o|--output)
            case "$2" in
                "") OUTPUTFOLDER='/default/input/here/' ; shift 2 ;;
                *) OUTPUTFOLDER=$2 ; shift 2 ;;
        esac ;;
        --) shift ; break ;;
    esac
done

echo "${MAPPE}"
echo "${OUTPUTFOLDER}"

#do something here..

that is, I have two optional argument flags -i/--input and -o/-output. I have a problem with the script currently:

To overwrite the default value of a flag, you need to write the value you want right after the flag, without any spaces. example: if i wanted to pass /c/ into -i and /f/ into -o, i would need to call the script as: bash argument_script.sh -i/c/ -o/f/. Notice the missing spaces. If I were to write bash argument_script.sh -i /c/ -o /f/ the variables MAPPE and OUTPUTFOLDER would be using the default values.
Can the script be rewritten, so the arguments passed into -i/-o needs to be written after a space (example: bash argument_script.sh -i /c/ -o /f/)

2
  • Do you have a #! line as the first line of your script? Commented Jan 13, 2021 at 9:24
  • That can't work. If you allow the next argument after -i to be its value, then there's no other way to pass -i without value than by making sure -i is the last argument. As cmd -i -o would be -i with -o as the option's argument (like --input=-o) Commented Jan 13, 2021 at 10:52

2 Answers 2

2

The behavior you describe is because you have an extra : in your getopt. Just change your getopt line to this and it will work:

VARS=`getopt -o i:o: --long input:,output: -- "$@"`

However, this is a very, very convoluted way of writing your script. Here is a simpler version (also correcting some bad practices like capitalized variables):

#!/bin/bash

##argument_script.sh
vars=$(getopt -o i:o: --long input:,output: -- "$@")
eval set -- "$vars"

mappe='/default/input/here/'
outputFolder='/default/input/here/'

# extract options and their arguments into variables.
for opt; do
    case "$opt" in
      -i|--input)
        mappe=$2
        shift 2
        ;;
      -o|--output)
        outputFolder=$2
        shift 2
        ;;
    esac
done
echo "mappe: $mappe"
echo "out: $outputFolder"

You can now do:

$ ./argument_script.sh -i /c/  -o /f/
mappe: /c/
out: /f/

Note that it also works if you run ./argument_script.sh -i/c/ -o/f/. The space is not required, it is allowed.

4
  • There's something I'd like to understand. The loop that processes the options is looping over the positional arguments. The first element is $1. After the first iteration, you might have shifted, so the next thing to process is still at $1. But the next iteration would be with $2. So I would think, you'd miss it. But that's not the case. Whats going on?
    – leo
    Commented Oct 9, 2023 at 4:22
  • I think I get it. The for iterates over the original "$@", that one is not updated.
    – leo
    Commented Oct 9, 2023 at 4:51
  • @leo The point of the shift is to remove the next parameter, the value, so we move to the next argument. Remember, we are processing something like -i /c/ -o /f/ so when we see the -i, we want to jump to the -o, not keep processing the /c/. But for loops don't jump back to the beginning and repeat everything, that's the point of for loops: they iterate over all the elements one by one. They don't go back to the 1st element.
    – terdon
    Commented Oct 9, 2023 at 9:32
  • Right. I initially assumed that the for would operate with the updated array after the stripping of the elements with shift. That's not the case. I get it now.
    – leo
    Commented Oct 12, 2023 at 3:45
0

That's the expected behaviour, unfortunately, as implemented by the GNU getopt(3) function the getopt(1) utility is based on. From its manpage (emphasis mine):

Two colons mean an option takes an optional arg; if there is text in the current argv-element (i.e., in the same word as the option name itself, for example, -oarg), then it is returned in optarg, otherwise optarg is set to zero.

If you want to be able to use a space between the option and its argument (e.g. -o value instead of -ovalue) you have to make its argument non-optional, by using a single : after the option char in the spec.

1
  • 1
    The option is still optional. If you use the version in my answer, both options are optional. You can't pass the option with no argument but that is normal: this isn't an option that makes sense without an argument.
    – terdon
    Commented Jan 13, 2021 at 11:02

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .