1

Suppose we have the file ./testing with contents

#!/bin/bash
# Flags
    # Source: https://stackoverflow.com/a/7948533/31298396
TEMP=$(getopt -o ''\
              --long first,second \
              -n 'testing' -- "$@")

if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi

# Note the quotes around '$TEMP': they are essential!
eval set -- "$TEMP"

# Defaults
FIRST=false
SECOND=false

while true; do
  case "$1" in
    --first ) FIRST=true; shift 2 ;;
    --second ) SECOND=true; shift 2 ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

echo "$FIRST"
echo "$SECOND"

We now run ./testing --first --second. It returns

true
false

rather than

true
true

How can I fix this?

2
  • 2
    Shouldn't you be using shift rather than shift 2 (since neither of your options expects an argument)? Commented Nov 9 at 3:00
  • Oh oops you're absolutely right Commented Nov 9 at 3:39

1 Answer 1

6

shift 2 will remove the first 2 positional parameters so skip both --first and --second. You'd use shift 2 when processing an option that takes an argument (after having stored the value in $2). Here, for an option that doesn't take arguments, use shift, short for shift 1.

Instead of using the bash shell and the getopt command (here assumed to be the one from util-linux or compatible), you could also use a shell with builtin support for long option parsing such as zsh or ksh93:

#! /bin/zsh -
usage() {
  print -ru2 "Usage: $ZSH_SCRIPT [--first] [--second]"
  exit 1
}
zparseopts -D -F -A opt -- -first -second || usage
print "first=$+opt[--first] second=$+opt[--second]"
print -r Remaining args: "$@"

if (( $+opt[--second] )) print -- --second was given
#! /bin/ksh93 -
first=false second=false

while getopts '
  [-?myscript 1.0]
  [-author?Grass]
  [+NAME?myscript - maybe do something first then second]
  [+DESCRIPTION?Example description]
  [=1:first?Do the first thing.]
  [=2:second?Do the second thing.]

  [ <extra-arg>... ]' opt; do
  case $opt in
    (1) first=true;;
    (2) second=true;;
    (*) exec -- "$0" '-?';;
  esac
done
shift "$(( OPTIND - 1 ))"

print "first=$first second=$second"
print -r Remaining args: "$@"

if "$second"; then print -- --second was given; fi

With that one, you even get usage and man page for (almost) free:

$ ./myscript --help
Usage: ./myscript [ options ] [ <extra-arg>... ]
 Help: ./myscript [ --help | --man ] 2>&1
OPTIONS
  --first         Do the first thing.
  --second        Do the second thing.
$ ./myscript --man
NAME
  myscript - maybe do something first then second

SYNOPSIS
  myscript [ options ] [ <extra-arg>... ]

DESCRIPTION
  Example description

OPTIONS
  --first         Do the first thing.
  --second        Do the second thing.

IMPLEMENTATION
  version         myscript 1.0
  author          Grass

Beware all three (util-linux getopt, zsh's zparseopts and ksh93's getopts) parse options slightly differently between each other.


Note that [ $? != 0 ] (which should have better been written [ "$?" -ne 0 ]) is a command that negates the exit status of the previous command. To inverse the status, the ! keyword is more idiomatic:

if
  ! parsed_options=$(
    getopt -o '' --long first,second -n 'testing' -- "$@"
  )
then
  echo>&2 Terminating...
  exit 1
fi
eval "set -- $parsed_options"

Or here use the even more idiomatic parsed_options=$(...) || usage (with usage defined as a function that prints the usage on stderr and exits with failure and that can be reused any time wrong usage is detected).

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.