In bash, is there a way I can make shorter (if or) statements?
Solution 1:
It looks like a bug in your code, but we can refactor it down really small...
If you had really written your expression with [ foo = bar -o foo = car -o foo = jar -o foo = foo ]
, then it could've been simplified to:
echo yes
This test is always true because foo = foo
is always true, which in turn is because the literal string foo
is always the same as itself.
Though you've clarified this syntax didn't appear in code you were really using, I've kept this introductory section as it's a common mistake to do so, especially among novices. Attempting to check the value of foo
this way doesn't work in any Bourne-style shell--to examine the value of a variable with [
, you must perform parameter expansion with $
(see below). This particular aspect of the problem is not shell-specific; for example, here's a test in 7 Bourne-style shells.
Fixing the Original Code
Perhaps instead you meant to check the value of the foo
variable (i.e., its contents, rather than its name) against those strings. In that case, your original code would become like:
if [ "$foo" = bar -o "$foo" = car -o "$foo" = jar -o "$foo" = foo ]; then
echo yes
fi
This can be shortened slightly with the short-circuit &&
operator:
[ "$foo" = bar -o "$foo" = car -o "$foo" = jar -o "$foo" = foo ] && echo yes
But it's still longer and more complicated and repetitive than you probably prefer. So, moving on...
Shorter Forms via Pattern Matching
You can use the [[
facility's built-in regular expression matching operator:
[[ $foo =~ ^(bar|car|jar|foo)$ ]] && echo yes
While using case
is probably the most common way, [[
with =~
is:
- the same length as a compact use of
case
(see below) - arguably truer to the logic of your original code, in that it uses
[[
, which works much like a test expression.
So you might prefer that. It's the method I would probably use, to do this in bash.
If you wanted to do something like that but match using grep
instead of [[
with =~
, this is similar:
grep -Eq '^(bar|car|jar|foo)$' <<< "$foo" && echo yes
Or this:
grep -Fqe{bar,car,jar,foo} <<< "$foo" && echo yes
A One-Liner with case
As Serg says, can be simplified using case
instead of if
and [
. That's the classic and probably the most common way.
The method in Serg's answer works perfectly well, but for the particular code you're refactoring, we can write something simpler, by using only one pattern for case
. There is no need to have a catch-all *)
match at the end. If the string doesn't match any of the patterns, control simply falls through the case construct and no commands are executed, which is equivalent to your if
construct with no else
.
case $foo in bar|car|jar|foo) echo yes;; esac
That makes it short and simple enough to easily fit--and be readable--on a single line (which seems to be what you're looking for). This method is also portable to Bourne-style shells other than bash.
Solution 2:
I suggest using case structure. Something like:
case $foo in
foo|car|bar|jar) echo yes ;;
*) ;;
esac
If you like, you can add a command between *)
and ;;
to be executed when $foo
doesn't match foo
, car
, bar
, or jar
. For example, you could print a message with echo 'wrong input'
or something like that.
Solution 3:
Even shorter:
[[ $foo =~ ^([bcj]ar|foo)$ ]] && echo yes
This way $foo
is matched against a regex either starting with b
, c
or j
followed by ar
or matching exactly foo
.
This uses the &&
operator as a short-circuit to run echo yes
only if the [[ $foo =~ ^([bcj]ar|foo)$ ]]
expression evaluates to true.
If this is too compact in order to be read with ease, you can still use the full if construct, which does exactly the same but is more readable:
if [[ $foo =~ ^([bcj]ar|foo)$ ]]
then
echo yes
fi