3

The code below is an mwe based on a script mwe I've written where I specify flags in what I think is the usual way. But I'm seeing really wierd behavior. If I type mwe -e or mwe -n it thinks there are no arguments and returns no arg. If I type mwe -k or mwe -i it thinks argType is not "-" and returns breaking. If I comment out the four lines that end with a # the code works as expected. That suggests the problem is being caused by the while loop. Could somebody please explain what's happening?

#!/bin/bash
foo=0
argType=`echo "$1" | cut -c 1`
while [ 1 -gt 0 ] ;   #
    do   #
        if [ $# -eq 0 ] ; then
            echo no arg
            exit
        elif [ "$argType" != "-" ] ; then
            #No more flags
            echo breaking
            break  #
        elif [ "$1" = "-n"  ] ; then
            foo=1
            shift
        elif [ "$1" = "-e"  ] ; then
            foo=2
            shift
        elif [ "$1" = "-i"  ] ; then
            foo=3
            shift
        elif [ "$1" = "-k"  ] ; then
            foo=4
            shift
        fi
done  #
echo This is foo:  $foo
Leo Simon
  • 443
  • 1
  • 5
  • 11
  • 2
    `-n` and `-e` are likely being consumed as arguments to `echo`... – steeldriver May 31 '20 at 21:29
  • 2
    Are you aware that `getopts` will handle most of this for you? – roaima May 31 '20 at 22:24
  • See also: [How can I handle command-line options and arguments in my script easily?](https://mywiki.wooledge.org/BashFAQ/035#getopts) in BashFAQ, [An example of how to use getopts in bash](https://stackoverflow.com/questions/16483119/an-example-of-how-to-use-getopts-in-bash) on SO, and, [the POSIX description of `getopts`](https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/getopts.html), which also has an example of usage. – ilkkachu May 31 '20 at 22:37

2 Answers2

6

Third line should be

argType=$(printf "%s" "$1" | cut -c 1)

As mentioned in comments, echo interprets arguments such as -e as an option, so that -e would not be passed to cut. Worse, as a special case, end-of-options flag -- is not available to echo. Then, you need printf, which is usually better anyway.

Since you are in bash, you could adopt @steeldriver's suggestion and use argType=${1:0:1} (which means: For parameter 1, start at char 0 and get 1 char) instead of the pipeline. Notice that is not available in the POSIX shell, however.

Also prefer $() instead of backticks, since the latter harm readability, especially when nesting.

Finally, notice that you are shifting, so, even after that correction, if you try ./myscript -e -i, eventually [ $# -eq 0 ] will be true and the execution will be terminated by exit. Maybe that is intended, maybe not, but at the end of the day the last echo would not be triggered.

Quasímodo
  • 18,625
  • 3
  • 35
  • 72
  • 1
    Also, the assignment to `argType` is before the loop, so it doesn't get updated as options are processed; it needs to be moved inside the loop to work right. Or, since this is bash, you could just use `elif [[ "$1" != "-"* ]]; ...` (note that this type of pattern matching requires a double-bracket conditional expression instead of just a single-bracket test). – Gordon Davisson May 31 '20 at 22:33
  • 1
    If they're using [a POSIX shell, they could use `getopts`](https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/getopts.html)... Also, there's the hilarious `"${1%"${1#?}"}"` that works in stead of `${1:0:1}` – ilkkachu May 31 '20 at 22:33
2

From your question it is not clear what you want !

Anyhow it seems that you want the number corresponding to the last argument

#!/bin/bash
foo=0;

while [[ $# -gt 0 ]]; do
    case "${1}" in
        '-n')
            foo=1;
            shift
        ;;
        '-e')
            foo=2;
            shift
        ;;
        '-i')
            foo=3;
            shift
        ;;
        '-k')
            foo=4;
            shift
        ;;
        *)
            echo "Invalid flag";
            exit 1;
        ;;
    esac
done

echo "This is foo: $foo"

If instead you want a mechanism that treats and validates arguments before being processed, you can use something like

#!/bin/bash

inputf='';
outputf='';
text='';
format='';

while [[ $# -gt 0 ]];do
    case "${1}" in
        '-i')
            inputf="${2}";
            shift 2
        ;;
        '-o')
            outputf="${2}";
            shift 2
        ;;
        '-t')
            text="${2}";
            shift 2
        ;;
        '-f')
            format="${2}";
            shift 2
        ;;
    esac
done
AdminBee
  • 21,637
  • 21
  • 47
  • 71
Yunus
  • 1,634
  • 2
  • 13
  • 19
  • first suggestion is great, thanks. would you mind adding an extra case for me, which would deal with unintended flags, e.g., if I typed -G it would return "flag not recognized" and exit – Leo Simon Jun 16 '20 at 04:21
  • for other cases we use glob star `*` that will catche anything other than valid flags – Yunus Jun 16 '20 at 11:48