13

I'm trying to write a comparison in a while statement that's case insensitive. Basically, I'm simply trying to shorten the following to act on a yes or no question prompt to the user ...

while[ $yn == "y" | $yn == "Y" | $yn == "Yes" | $yn == "yes" ] ; do

What would be the best way to go about this?

codeforester
  • 39,467
  • 16
  • 112
  • 140
user2150250
  • 4,797
  • 11
  • 36
  • 44

5 Answers5

14

No need to use shopt or regex. The easiest and quickest way to do this (as long as you have Bash 4):

if [ "${var1,,}" = "${var2,,}" ]; then
  echo "matched"
fi

All you're doing there is converting both strings to lowercase and comparing the results.

Riot
  • 15,723
  • 4
  • 60
  • 67
  • I've just tried that and it worked. I am unsure though how. Could you link to the documentation or at least tell how that structure is named? – Angelo Fuchs Dec 12 '15 at 21:24
  • 1
    @AngeloFuchs the `${my_variable,,}` syntax is bash parameter expansion. See https://www.gnu.org/software/bash/manual/bashref.html#Shell-Parameter-Expansion and scroll to the bottom of that section for the case conversions. – Riot Dec 13 '15 at 00:54
13
shopt -s nocasematch
while [[ $yn == y || $yn == "yes" ]] ; do

or :

shopt -s nocasematch
while [[ $yn =~ (y|yes) ]] ; do

Note

  • [[ is a bash keyword similar to (but more powerful than) the [ command. See http://mywiki.wooledge.org/BashFAQ/031 and http://mywiki.wooledge.org/BashGuide/TestsAndConditionals
    Unless you're writing for POSIX sh, we recommend [[.
  • The =~ operator of [[ evaluates the left hand string against the right hand extended regular expression (ERE). After a successful match, BASH_REMATCH can be used to expand matched groups from the pattern. Quoted parts of the regex become literal. To be safe & compatible, put the regex in a parameter and do [[ $string =~ $regex ]]
Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
3

Here's an answer that uses extended patterns instead of regular expressions:

shopt -s nocasematch
shopt -s extglob
while [[ $yn = y?(es) ]]; do
  ...
done

Note that starting in version 4.1, bash always uses extended patterns inside the [[ ... ]] conditional expression, so the shopt line is no necessary.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • I think your '?' is in the wrong spot in your regex. That will match "yes" or "es" but not "y". –  Feb 20 '14 at 03:09
  • 1
    It's not a regular expression; it's an extended pattern. The `y` matches itself; `?(es)` matches zero or one occurrence of the pattern `es`. – chepner Feb 20 '14 at 13:51
1

try this one too:

 [[ $yn =~ "^[yY](es)?$" ]] 
Kent
  • 189,393
  • 32
  • 233
  • 301
  • And if I want the "es" to be case insensitive as well I could put `[[ $yn =~ "^[yY][eE][sS]" || $yn == [yY] ]]? I want to be able to accept "yEs" and "YeS" and all other combinations of case. – user2150250 Mar 21 '13 at 21:19
  • @sputnick take a look the question and example, only `y` is case insensitive, `es` part is only lower case... that what I understood.. – Kent Mar 21 '13 at 21:19
  • 1
    @user2150250 then I suggest you before going into the while loop, do a 'tr`, make the value all in lower/upper case. then you could save some effort. You could just compare if it equals `y` or `yes` (if in lower case) – Kent Mar 21 '13 at 21:32
  • If you are using Bash v4.x+ you can perform string case conversion without invoking tr. `yn=${yn,,}; [[ $yn =~ ^yes ]]` – Bruce Nov 17 '14 at 00:03
  • 1
    Quoting the right-hand side here actually suppresses interpretation as a regex (except for Bash 3.1 and older), so this wouldn't work as is for non-ancient Bash. The safe way is to declare the regex in a separate variable and then use that one in `[[ ]]`, unquoted. – Benjamin W. Sep 13 '18 at 22:36
1

I prefer using grep -E or egrep for short:

while egrep -iq '^(y|yes)$' <<< $yn; do
  your_logic_here
done

Here you have explanations from grep manual:

-i, --ignore-case

Ignore case distinctions in both the PATTERN and the input files. (-i is specified by POSIX.)

-q, --quiet, --silent

Quiet; do not write anything to standard output. Exit immediately with zero status if any match is found, even if an error was detected. Also see the -s or --no-messages option. (-q is specified by POSIX.)

Community
  • 1
  • 1
jirislav
  • 340
  • 6
  • 16
  • This works, but it's much more efficient to use bash-internal syntax than calling an external program. The difference won't matter for an interactive script, of course, since the human in the loop is orders of magnitude slower than the external process, but I still prefer using my scripting language's own syntax when it's easy enough. – joanis Oct 26 '20 at 15:51