69

In the middle of a script, I want to check if a given flag was passed on the command line. The following does what I want but seems ugly:

if echo $* | grep -e "--flag" -q
then
  echo ">>>> Running with flag"
else
  echo ">>>> Running without flag"
fi

Is there a better way?

Note: I explicitly don't want to list all the flags in a switch/getopt. (In this case any such things would become half or more of the full script. Also the bodies of the if just set a set of vars)

Dev2017
  • 857
  • 9
  • 31
BCS
  • 75,627
  • 68
  • 187
  • 294

11 Answers11

89

An alternative to what you're doing:

if [[ $* == *--flag* ]]

See also BashFAQ/035.

Note: This will also match --flags-off since it's a simple substring check.

Mikhail
  • 8,692
  • 8
  • 56
  • 82
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • 9
    Won't that also pick up false positives? e.g. if you chech for `*-f*` it will match `--force`, `-fish`, `--update-files`, `-w-t-f`etc – nathanchere May 30 '17 at 10:32
  • 2
    @nathanchere: careful choice of match string is important. Note that your example allows for all the matches that you list but mine leaves much fewer possibilities of false positives. Given the OP's requirement for a simple solution mine fits quite well. Otherwise use getopt or getopts. – Dennis Williamson May 30 '17 at 12:45
  • 2
    Exactly my point. Mine was an intentionally simplified example, but still demonstrates the same issue with yours. e.g. if you use `*--flag*` you will match `--flag`, `--flags-off` etc. i.e. it's a sloppy solution – nathanchere May 31 '17 at 09:22
  • 2
    @nathanchere You're welcome to post a better solution that meets the OP's requirements. – Dennis Williamson May 31 '17 at 11:08
  • Could you please tell how to get the ordinal of that `$*`? – Optimus Prime Jul 14 '17 at 06:56
  • @OptimusPrime: Why do you want the ordinal? – Dennis Williamson Jul 14 '17 at 16:03
  • Let's say the flag is at 5th position, then $6 would give me its value. – Optimus Prime Jul 18 '17 at 08:10
  • 1
    @OptimusPrime: The OP wanted a quick and dirty way to check for a flag without using `getopt`. For complete processing of flags with (and/or without) arguments, you should use techniques as described in the FAQ I linked to. – Dennis Williamson Jul 18 '17 at 10:22
  • Is there no need for `"$*"`? – Randoms Aug 04 '17 at 22:15
  • @Randoms: If you're asking whether there's a need for quoting - it's not necessary inside double square brackets. – Dennis Williamson Aug 23 '17 at 13:03
  • My small [modification](https://stackoverflow.com/a/65811916/8854890) that solves the mentioned problem of the false positives – Nick Vee Jan 20 '21 at 14:56
  • This does not work in Bash at all... – Cerin Jul 18 '22 at 18:20
  • 1
    @Cerin: Yes it does, but "does not work" conveys no information at all. What did you try and what was the result? – Dennis Williamson Jul 18 '22 at 22:14
  • @DennisWilliamson My mistake. Your solution only works in the top-level scope, where I was trying to use it inside a function, which of course redefines `$*`. Storing the top level arguments to a variable made it work for my case. – Cerin Jul 19 '22 at 23:21
20

I typically see this done with a case statement. Here's an excerpt from the git-repack script:

while test $# != 0
do
    case "$1" in
    -n) no_update_info=t ;;
    -a) all_into_one=t ;;
    -A) all_into_one=t
        unpack_unreachable=--unpack-unreachable ;;
    -d) remove_redundant=t ;;
    -q) GIT_QUIET=t ;;
    -f) no_reuse=--no-reuse-object ;;
    -l) local=--local ;;
    --max-pack-size|--window|--window-memory|--depth)
        extra="$extra $1=$2"; shift ;;
    --) shift; break;;
    *)  usage ;;
    esac
    shift
done

Note that this allows you to check for both short and long flags. Other options are built up using the extra variable in this case.

Kaleb Pederson
  • 45,767
  • 19
  • 102
  • 147
  • I think you have to remove the last 'shift' statement, right after the 'esac'. At least on my machine it doesn't work if it's present. – Thomas Krille Jul 30 '15 at 12:04
  • @ThomasKrille The shift at the end is necessary to move onto the next argument. If you remove it, it would run infinitely (unless you're using one of the options that have shift in the case). – scorgn Dec 04 '20 at 17:04
14

you can take the straight-forward approach, and iterate over the arguments to test each of them for equality with a given parameter (e.g. -t, --therizinosaurus).

put it into a function:

has_param() {
    local term="$1"
    shift
    for arg; do
        if [[ $arg == "$term" ]]; then
            return 0
        fi
    done
    return 1
}

… and use it as a predicate in test expressions:

if has_param '-t' "$@"; then
    echo "yay!"
fi

if ! has_param '-t' "$1" "$2" "$wat"; then
    echo "nay..."
fi

if you want to reject empty arguments, add an exit point at the top of the loop body:

for arg; do
    if [[ -z "$arg" ]]; then
        return 2
    fi
    # ...

this is very readable, and will not give you false positives, like pattern matching or regex matching will.
it will also allow placing flags at arbitrary positions, for example, you can put -h at the end of the command line (not going into whether it's good or bad).


but, the more i thought about it, the more something bothered me.

with a function, you can take any implementation (e.g. getopts), and reuse it. encapsulation rulez!
but even with commands, this strength can become a flaw. if you'll be using it again and again, you'll be parsing all the arguments each time.

my tendency is to favor reuse, but i have to be aware of the implications. the opposed approach would be to parse these arguments once at the script top, as you dreaded, and avoid the repeated parsing.
you can still encapsulate that switch case, which can be as big as you decide (you don't have to list all the options).

Eliran Malka
  • 15,821
  • 6
  • 77
  • 100
7

You can use the getopt keyword in bash.

From http://aplawrence.com/Unix/getopts.html:

getopt

This is a standalone executable that has been around a long time. Older versions lack the ability to handle quoted arguments (foo a "this won't work" c) and the versions that can, do so clumsily. If you are running a recent Linux version, your "getopt" can do that; SCO OSR5, Mac OS X 10.2.6 and FreeBSD 4.4 has an older version that does not.

The simple use of "getopt" is shown in this mini-script:

#!/bin/bash
echo "Before getopt"
for i
do
  echo $i
done
args=`getopt abc:d $*`
set -- $args
echo "After getopt"
for i
do
  echo "-->$i"
done
BCS
  • 75,627
  • 68
  • 187
  • 294
WhirlWind
  • 13,974
  • 3
  • 42
  • 42
  • It's better to use the builtin `getopts` rather than the external `getopt`. – Dennis Williamson May 20 '10 at 17:12
  • 1
    @Dennis: `getopts` supports long option names like `--flag`? – indiv May 20 '10 at 17:30
  • 1
    @indiv: Oh, sorry, I overlooked that requirement. I would use a `case` statement before I would use `getopt`. See [this](http://aplawrence.com/Unix/getopts.html) for a comparison of `getopt` and `getopts`. – Dennis Williamson May 20 '10 at 17:51
  • 1
    From the faq in the above answer: getopts: Unless it's the version from util-linux, and you use its advanced mode, never use getopt(1). Traditional versions of getopt cannot handle empty argument strings, or arguments with embedded whitespace. – Ajax Dec 29 '15 at 02:50
4

I've made small changes to the answer of Eliran Malka:

This function can evaluate different parameter synonyms, like "-q" and "--quick". Also, it does not use return 0/1 but an echo to return a non-null value when the parameter is found:

function has_param() {
    local terms="$1"
    shift

    for term in $terms; do
        for arg; do
            if [[ $arg == "$term" ]]; then
                echo "yes"
            fi
        done
    done
}

# Same usage:

# Assign result to a variable.
FLAG_QUICK=$(has_param "-q --quick" "$@")  # "yes" or ""

# Test in a condition using the nonzero-length-test to detect "yes" response.
if [[ -n $(has_param "-h --help" "$@") ]]; then; 
    echo "Need help?"
fi

# Check, is a flag is NOT set by using the zero-length test.
if [[ -z $(has_param "-f --flag" "$@") ]]; then
    echo "FLAG NOT SET"
fi
Philipp
  • 10,240
  • 8
  • 59
  • 71
  • nice! piping the data back as output is an awesome addition - it streamlines the command api greatly, and adding the ability to check a list of terms is also quite cool (though the external loop will further strain on performance..). <3 – Eliran Malka Oct 19 '21 at 01:48
  • 1
    meta - sometimes stackoverflow is a bit like github. should we add that `SO fork` feature already and be done with it? :D – Eliran Malka Oct 19 '21 at 01:51
  • 1
    Note, if your doing the check inside a function, you can't use "$@", because that will refer to the function's arguments. You'd need to cache the script's arguments in a global variable. – Cerin Jul 18 '22 at 18:23
4
if [ "$1" == "-n" ]; then
    echo "Flag set";
fi
user3263338
  • 215
  • 1
  • 3
  • 6
3

The modification of Dennis Williamson's answer with additional example for a argument in the short form.

if [[ \ $*\  == *\ --flag\ * ]] || [[ \ $*\  == *\ -f\ * ]]

It solves the problem of false positive matching --flags-off and even --another--flag (more popular such case for an one-dashed arguments: --one-more-flag for *-f*).

\ (backslash + space) means space for expressions inside [[ ]]. Putting spaces around $* allows to be sure that the arguments contacts neither line's start nor line's end, they contacts only spaces. And now the target flag surrounded by spaces can be searched in the line with arguments.

Nick Vee
  • 621
  • 2
  • 7
  • 17
1

Here is a variation on the most voted answer that won't pick up false positives

if [[ " $* " == *" -r "* ]]; then
gilad905
  • 2,842
  • 2
  • 16
  • 23
0

Not an alternative, but an improvement, though.

if echo $* | grep -e "\b--flag\b" -q

Looking for word boundaries will make sure to really get the option --flag and neither --flagstaff nor --not-really--flag

snaeqe
  • 9
  • 1
  • 1
    This will not work. E.g. `echo '--foo-bar' | grep -e "--foo\b"` fails. See explanation of why [here](https://www.regular-expressions.info/wordboundaries.html) – jesusbriales Jul 29 '22 at 12:51
0

One more variation:

flag1="false"
if [[ " $@ " =~ " --flag1 " ]]; then
    flag1="true"
fi

flag2="false"
if [[ " $@ " =~ " --flag2 " ]]; then
    flag2="true"
fi
user553965
  • 1,199
  • 14
  • 15
0

Mb something like:

if [[ ($* == -f) || ($* == --flag) ]];
then
  echo ">>>> Running with flag"
else
  echo ">>>> Running without flag"
fi