121

I have the following piece of Bash script:

function get_cms {
    echo "input cms name"
    read cms
    cms=${cms,,}
    if [ "$cms" != "wordpress" && "$cms" != "meganto" && "$cms" != "typo3" ]; then
        get_cms
    fi
}

But no matter what I input (correct and incorrect values), it never calls the function again, because I only want to allow 1 of those 3 inputs.

I have tried it with ||, with [ var != value ] or [ var != value1 ] or [ var != value1 ], but nothing works.

Can someone point me in the right direction?

4
  • @triplee I voted to reopen. I believe the other question stackoverflow.com/questions/22259259 should be closed instead. This question is more clearly forumulated, came sooner, and has better answers than the other. Commented Jun 6, 2020 at 16:44
  • 1
    @studgeek Thanks for your suggestion; I have now done so. Commented Jun 6, 2020 at 17:56
  • The duplicate is better IMHO in that it has case as the accepted answer, but I guess this is mainly a matter of personal taste. Note that case is portable back to the original Bourne shell., and of course also to modern POSIX shell. Maybe see also Difference between sh and bash Commented Jun 6, 2020 at 18:00
  • 1
    Thanks. This one does have a case answer as well. It isn't the accepted one, but personally I always look at all the answers and their votes more then then which one is accepted (since the latter is just one person's opinion, while the votes are from many folks). Commented Jun 8, 2020 at 1:21

5 Answers 5

192

If the main intent is to check whether the supplied value is not found in a list, maybe you can use the extended regular expression matching built in BASH via the "equal tilde" operator (see also this answer):

if ! [[ "$cms" =~ ^(wordpress|meganto|typo3)$ ]]; then get_cms ; fi
Sign up to request clarification or add additional context in comments.

4 Comments

Extra points for the cleaner answer. I prefer putting the ! inside [[ ]] but both are equally valid.
Thank you so much for this answer. Helped me to solve an issue at work!
for some reason this doesn't work for me. I'm using (id=2015|id=2016) and I want the IF to execute if the variable has one of those strings anywhere, but it doesn't work
If you want to check the string values to be anywhere in the variable, you should remove ^ and $, for example: if [[ "$id" =~ (2015|2016) ]]; then ......
65

Maybe you should better use a case for such lists:

case "$cms" in
  wordpress|meganto|typo3)
    do_your_else_case
    ;;
  *)
    do_your_then_case
    ;;
esac

I think for long such lists this is better readable.

If you still prefer the if you can do it with single brackets in two ways:

if [ "$cms" != wordpress -a "$cms" != meganto -a "$cms" != typo3 ]; then

or

if [ "$cms" != wordpress ] && [ "$cms" != meganto ] && [ "$cms" != typo3 ]; then

8 Comments

it works great! btw, arent "do_your_then|else_case" text tip inverted? :)
OP was formulating his original condition using ≠ (!=); my version with the case is matching (so it is rather using a comparison with =), hence the inversion.
Beautiful solution. Are there any gotchas associated with using case like this?
Shell programming is never free of "gotchas", as you put it ;-) I can think of problems using comparison strings with spaces or pipe symbols or other strange characters in them; in this case you would need to quote them properly. This would take time to find the right syntax and would at least be hard to read for the next maintainer of the code.
@Alfe, see the [OB XSI] notation in pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html. If you click on it, it'll link you to a definition: The functionality described may be removed in a future version of this volume of POSIX.1-2008. Strictly Conforming POSIX Applications and Strictly Conforming XSI Applications shall not use obsolescent features. I doubt GNU coreutils or bash will actually remove it, but it is scheduled to in the future no longer be POSIX-mandated behavior (for good cause, given the ambiguities introduced).
|
58

Instead of saying:

if [ "$cms" != "wordpress" && "$cms" != "meganto" && "$cms" != "typo3" ]; then

say:

if [[ "$cms" != "wordpress" && "$cms" != "meganto" && "$cms" != "typo3" ]]; then

You might also want to refer to Conditional Constructs.

2 Comments

Repeating "$cms" != again and a again isn't very DRY. It leaves room for typos in any of the occasions (e. g. "$csm" !=), and you have no compiler to point that out to you in shells. Better try to avoid repeating the variable name. (See my answer for a way to do that.)
I had to remove the ; at the end so instead of being ... "typo3" ]]; then it is ..."typo3"]] then, but otherwise this worked just fine.
19

As @Renich suggests, you can also use extended globbing for pattern matching. So you can use the same patterns you use to match files in command arguments (e.g. ls *.pdf) inside of bash comparisons.

For your particular case you can do the following.

if [[ "${cms}" != @(wordpress|magento|typo3) ]]

The @ means "Matches one of the given patterns". So this is basically saying cms is not equal to 'wordpress' OR 'magento' OR 'typo3'. In normal regular expression syntax @ is similar to just ^(wordpress|magento|typo3)$.

Mitch Frazier has two good articles in the Linux Journal on this Pattern Matching In Bash and Bash Extended Globbing.

For more background on extended globbing see Pattern Matching (Bash Reference Manual).

Comments

9

Here's my solution

if [[ "${cms}" != @(wordpress|magento|typo3) ]]; then

2 Comments

Not + but @ in your extglob. Otherwise you'll get wrong results for, e.g., wordpressmagento or typo3typo3.
@gniourf_gniourf I believe Edgar Grill answer is the same problem using ^ can result in matching any result that begins with "wordpress..." etc.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.