0

Let's say I have the program test.sh, which has parameters, some optional and some not.

For example:

./test.sh --foo /path/to/file --baz --bar1 --bar2 --bar3

where foo and baz, as well as the path, are necessary and the bars are optional parameters.

Now, I want to be able to make the everything after the path order-insensitive.

I could use

if [[ "$3" == "--baz" ]] || [[ "$4" == "--baz" ]] || ... || [[ "${(n-1)}" == "--baz" ]] || [[ "${n}" == "--baz" ]]

but that's slow and messy, even for me.

Ideally I would have something along the lines of this:

if [[ ${n > 2} == "--baz" ]]; then
SFR
  • 96
  • 11
  • 2
    Use `getopt` (not the shell built-in `getopts`, but the GNU program) to parse your command line arguments. – chepner Jun 05 '20 at 20:30
  • @chepner Won't that search through the entire command, though, as opposed to specific parts of it? – SFR Jun 05 '20 at 20:39
  • Is there an missing argument after `--baz`? What's the point of a mandatory argument that doesn't convey any information? – chepner Jun 05 '20 at 20:43
  • @chepner Maybe I should've phrased that better; my intent was "one of a couple of options where it's necessary to choose one of them". It could also work with an additional argument, though -- I'm generalising my problem for future readers as this isn't the part I'm stuck at – SFR Jun 05 '20 at 20:48
  • 2
    Regardless, using something like `getopt` is better than reinventing the wheel. – chepner Jun 05 '20 at 20:51
  • CLI argument handling best practices: (a) Don't reinvent the wheel. Use an off-the-shelf parser like `getopt` that handles short and long arguments, argument reordering, combining short options, `--` to end option parsing, etc. (b) Don't require any particular order. Parse all the arguments first then check if all the required ones were given at the end. – John Kugelman Jun 05 '20 at 21:09
  • If done right, you can make everything order insensitive . – Mike Q Jun 06 '20 at 03:13

3 Answers3

0

Violating standard utility syntax guidelines like this makes your program unpredictable and difficult to use, but you can still do so e.g. with a simple utility function:

hasArgAfter() {
  n="$1"
  word="$2"
  shift "$((n+2))"
  for arg
  do
    [ "$arg" = "$word" ] && return 0
  done
  return 1
}

if hasArgAfter 2 "--baz" "$@"
then
  echo "Some argument strictly after #2 is --baz"
fi
that other guy
  • 101,688
  • 11
  • 135
  • 166
0

Based on the answers in:

You could come up with the following answer:

[[ " ${@:3} " =~ " --baz " ]] && echo yes || echo no

This might fail if you have something like

command --foo /path/to/file --flag1 option1 --flag2 "foo --baz bar" --flag3

where foo --baz bar is an option to --flag2

Another way, a bit safer, would be:

for arg in "${@:3}"; do
   [[ "${arg}" != "--baz" ]] && continue
   # perform action here if --baz is found
   set-of-commands
   # need this line for early return
   break
done
kvantour
  • 20,742
  • 4
  • 38
  • 51
0

Create a function for handling inputs, then ordering won't matter.

What I would do is define global variables for your script using the typset command at the top of your script. Then I would handle the user input options using a function or just code it without a function. This way when input is not in order or it is missing it is properly handled.

The example below is using a case statement and it is using the build in shift option to go through all of the inputs. $1 is --option $2 is the value such as "/path/to/something", I have checks in there check if $2 is empty "-z" or || if it isn't, set it. When done items are either set or empty. In your code you will check if set or empty to determine if you are going to use that variable (not shown here.

# -- create globals --
typeset fooPath
typeset baz
typeset bar1

# -- get required commandline input --
get_user_input_options() {
  while [[ $# -gt 0 ]] ;do
    key="$1"
    case ${key,,} in
       --foo|--foo-path)
        fooPath="${2}"
        shift
      ;;
      -b|--baz)
        [[ -z "${2}" ]] || baz="${2}"
        shift
      ;;
      --bar1)
        [[ -z "${2}" ]] || bar1="${2}"
        shift
      ;;
      *) echo "ERROR: Unknown option $key given."
        exit 9
      ;;
      esac
      shift
    done
}
# -- get inputs first in script logic --
get_user_input_options "$@"
echo $fooPath
echo $baz
echo $bar1

Example outputs:

[centos@ip-172-31-22-252 ~]$ ./t.sh --foo "/some/thing" --baz askdh
/some/thing
askdh

[centos@ip-172-31-22-252 ~]$ ./t.sh --foo "/some/thing" --baz askdh --bar1
/some/thing
askdh

[centos@ip-172-31-22-252 ~]$ ./t.sh --foo "/some/thing" --baz askdh --bar1 test
/some/thing
askdh
test
[centos@ip-172-31-22-252 ~]$ ./t.sh --foo "/some/thing" --baz askdh --bar1 test --notvalid askdha
ERROR: Unknown option --notvalid given.
Mike Q
  • 5,006
  • 2
  • 41
  • 53