109

I'm trying to write a script where I want to check if any of the parameters passed to a bash script match a string. The way I have it setup right now is

if [ "$3" != "-disCopperBld" -a "$4" != "-disCopperBld" -a "$5" != "-disCopperBld" -a "$6" != "-disCopperBld"]

but there might be a large number of parameters, so I was wondering if there is a better way to do this?

Thanks

EDIT: I tried this chunk of code out, and called the script with the option, -disableVenusBld, but it still prints out "Starting build". Am I doing something wrong? Thanks in advance!

while [ $# -ne 0 ]
do
    arg="$1"
    case "$arg" in
        -disableVenusBld)
            disableVenusBld=true
            ;;
        -disableCopperBld)
            disableCopperBld=true
            ;;
        -disableTest)
            disableTest=true
            ;;
        -disableUpdate)
            disableUpdate=true
            ;;
        *)
            nothing="true"
            ;;
    esac
    shift
done

if [ "$disableVenusBld" != true ]; then
    echo "Starting build"
fi
iman453
  • 1,435
  • 2
  • 13
  • 13
  • Thanks for the replies guys, I appreciate ya'll taking time out to help me. I tried a chunk of code, but I can't seem to figure out what's going wrong. Any ideas? (I've pasted the code in an edit of my original post) – iman453 Sep 09 '10 at 15:29
  • Hmm: it works for me. I added `#! /bin/sh -` to the top of what you've included there, made the script executable, then `./t.sh` prints "Starting build", but `./t.sh -disableVenusBld` prints nothing. – Norman Gray Sep 09 '10 at 21:47
  • `for a in "$@"; do [[ "$a" == "-disCopperBld" ]] && echo match; done` – bvj Sep 19 '20 at 00:50

5 Answers5

84

It looks like you're doing option handling in a shell script. Here's the idiom for that:

#! /bin/sh -

# idiomatic parameter and option handling in sh
while test $# -gt 0
do
    case "$1" in
        --opt1) echo "option 1"
            ;;
        --opt2) echo "option 2"
            ;;
        --*) echo "bad option $1"
            ;;
        *) echo "argument $1"
            ;;
    esac
    shift
done

exit 0

(There are a couple of conventions for indenting the ;;, and some shells allow you to give the options as (--opt1), to help with brace matching, but this is the basic idea)

Norman Gray
  • 1,326
  • 9
  • 7
  • 6
    A `shift` statement is used when the number of arguments to a command is not known in advance, for instance when users can give as many arguments as they like. In such cases, the arguments are processed in a `while` loop with a test condition of `$#`. This condition is true as long as the number of arguments is greater than zero. The `$1` variable and the `shift` statement process each argument. The number of arguments is reduced each time `shift` is executed and eventually becomes zero, upon which the `while` loop exits. [source](http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_09_07.html) – Serge Stroobandt Jan 22 '15 at 22:47
  • Here is a similar example with additional [`echo $1 | sed` argument value processing](http://stackoverflow.com/a/7069755/2192488). – Serge Stroobandt Jan 22 '15 at 22:57
  • If you like me think that loops in scripted languages are not a good idea, here's an alternative (the answer below): https://superuser.com/a/186304/1276328 – mirekphd May 25 '22 at 10:22
80

This worked for me. It does exactly what you asked and nothing more (no option processing). Whether that's good or bad is an exercise for the poster :)

if [[ "$*" == *"YOURSTRING"* ]]
then
    echo "YES"
else
    echo "NO"
fi

This takes advantage of special handling of $* and bash super-test [[]] brackets.

Toto
  • 17,001
  • 56
  • 30
  • 41
Rich Homolka
  • 31,057
  • 6
  • 55
  • 80
  • 3
    IMHO this had to be the most correct answer, since the question requires only to check the presence of a parameter. I have edited, however, changing `$*` to `$@`, since the to-be-tested string might have spaces and added link to bash's documentation about it. – h7r Feb 01 '15 at 12:51
  • 9
    Did you mean to use the =~ operator? Otherwise I don't see why this should work, and indeed it doesn't work when I try the exact script. – Seppo Enarvi Jul 14 '15 at 08:37
  • 3
    Not working. `bash -c 'echo args=$*; [[ "$@" == "bar" ]] && echo YES || echo NO' -- foo bar` – Tobia Nov 04 '15 at 11:55
  • 2
    This compares the entire argument list with `your string`. I think you need to do what is suggested by [this answer](http://stackoverflow.com/a/28263100). i.e: `bash -c 'echo args=$*; for i in "$@" ; do [[ $i == "bar" ]] && echo "Is set!" && break ; done' -- bar foo` would work. – starfry Dec 19 '16 at 20:23
  • 6
    This answer has been wrong for the past four years because h7r’s edit broke it.  The original answer (which I have now restored) works, provided “your string” contains no glob characters, and with some false positives.  For example, if the command is ``create a new certificate`` and “your string” is `cat`, this will report a match because `certificate` *contains* `cat`. – Scott - Слава Україні May 22 '19 at 19:46
  • 10
    Note for noobs like me: the YOURSTRING might be quoted, but the asterisks must be OUTSIDE the quotes, e.g. `if [[ "$*" == *"my search string"* ]]` – velis Jun 28 '19 at 07:20
  • 2
    This didn't work until i read @velis comment and realized the *YOURSTRING* needs those asterisks. Maybe edit answer for clarity? – Evan Morrison Mar 23 '21 at 04:52
  • 1
    I add spaces to the param string and to the matching term wanted, to make sure, it's not a partial match of another parameter (like "format" as part of "noformat" for example). `[[ ! " $@ " =~ [[:space:]]foo[[:space:]] ]] && echo "do something"` – Frank N Mar 26 '22 at 08:49
  • Interesting is that asterisks seems to be necessary. Without it, it not worked for me. Any logical explanation to remember this? Can edit answer to explain this? – 16851556 Aug 05 '23 at 15:56
9

How about searching (with wildcards) the whole parameter space:

if [[ $@ == *'-disableVenusBld'* ]]
then

Edit: Ok, ok, so that wasn't a popular answer. How about this one, it's perfect!:

if [[ "${@#-disableVenusBld}" = "$@" ]]
then
    echo "Did not find disableVenusBld"
else
    echo "Found disableVenusBld"
fi

Edit2: Ok, ok, maybe this isn't perfect... Think it works only if -param is at the start of the list and will also match -paramXZY or -paramABC. I still think the original problem can be solved very nicely with bash string manipulation, but I haven't quite cracked it here... -Can you??

Rich
  • 223
  • 2
  • 6
  • Your second suggestion — comparing the filtering substitution to the full substitution — works reasonably well, and has the advantage of not disrupting the list of arguments. – Donal Fellows May 30 '17 at 13:46
  • Could you explain what the second suggestion (especially `${@#`) does? – velop Nov 14 '17 at 16:01
  • 1
    @velop Sure! So you know that `$@` is a special variable that holds all of the command line arguments? Well I've just used Bash string manipulation on that variable to remove the substring "-disableVenusBld", and then I compare it to the original `$@`. So if `$@` equals `-foo -bar` then `${@#-disableVenusBld}` would still be `-foo -bar`, so I can see that the flag I'm looking for isn't present. However, if `$@` equals `-foo -disableVenusBld -bar` then `${@#-disableVenusBld}` would be `-foo -bar` which is not equal to `$@`, thus telling me that the flag I'm looking for is present! Cool eh! – Rich Nov 14 '17 at 20:23
  • @velop Learn more about Bash string manipulation here: http://tldp.org/LDP/abs/html/string-manipulation.html – Rich Nov 14 '17 at 20:29
  • @Rich pretty neat ^^, thx for the explanation. – velop Nov 17 '17 at 10:21
  • This doesn't actually work - `${@#` only removes the string from the *beginning*, so the example `-foo -disableVenusBld -bar` is unchanged – Nye Dec 20 '17 at 11:19
  • @Nye oh god, you're right! Don't know how I missed this.... so instead of `"${@#-disableVenusBld}"` I should've used `"${@/-disableVenusBld/}"` I suppose...? – Rich Dec 20 '17 at 15:12
  • Note that also `'-disableVenusBldFOO'` would also match the above, because the `${@#...}` parameter expansion construct nullifies the matched-prefix of each element. – ankostis Jun 04 '18 at 18:44
5
[[ "$@" =~ '-disableVenusBld' ]] && disableVenusBld=true
[[ "$@" =~ '-disCopperBld' ]] && disCopperBld=true
[[ "$@" =~ '-disableTest' ]] && disableTest=true
[[ "$@" =~ '-disableUpdate' ]] && disableUpdate=true

or more generally

[[ "$@" =~ 'your-string' ]] && ( doSomething )
user1016765
  • 159
  • 1
  • 3
4
disCopperBld=
for x; do
  if [ "$x" = "-disCopperBld" ]; then disCopperBld=1; break; fi
done
if [ -n "$disCopperBld" ]; then
  ...
fi

If you need to test only the parameters starting at $3, do the search in a function:

## Usage: search_trailing_parameters NEEDLE NUM "$@"
## Search NEEDLE amongst the parameters, skipping $1 through ${$NUM}.
search_trailing_parameters () {
  needle=$1
  shift $(($2 + 2))
  for x; do
    if [ "$x" = "$needle" ]; then return 0; fi
  done
  return 1
}
if search_trailing_parameters -disCopperBld 2 "$@"; then
  ...
fi

But I wonder why you're trying to do this in the first place, it's not a common need. Usually, you'd process options in order, as in Dennis's answer to your previous question.

Gilles 'SO- stop being evil'
  • 69,786
  • 21
  • 137
  • 178