18

Trying to have a variable meet a string of text that varies by length. The part of the string that matters is the first couple of characters.

var1=hello  
if [ $var1 == hello ]; then  
    echo success  
fi

Or

var1=hello  
if [ $var1 == h*o ]; then  
    echo success  
fi

Outputs: success

But since I care care about the first 3 characters or so, this sounds logical, but is not working:

var1=hello  
if [ $var1 == hel* ]; then  
    echo success  
fi

Outputs: -bash: [: too many arguments

Since I only care about the first couple of characters, I can do:

var1=hello 
if [ ${var1:0:3} == hel ]; then 
    echo success  
fi

That would work, but I am looking for an explanation of why I am getting that error and a possible better written solution.

EAG08
  • 181

6 Answers6

20

When you use * in an if like that, it will do filename expansion. So in the first instance, h*o probably matched to a file in your current directory, and so the test passed.

The hel* matched to several files, and so became too many arguments for the if command.

Try using if [[ $var1 == hel* ]]; then

The double brackets turns the test into a regex, and the * wildcard will work as you expect.

Paul
  • 61,193
4

Use double brackets to enable pattern matching in the right expression

if [[ "$var1" == hel* ]]

or use regular expressions comparison operator.

if [[ "$var1" =~ ^hel.* ]]

(Pattern matching and regular expressions are not the same)

Rohit Gupta
  • 5,096
2

I have a trick I use to hack in regex without using builtin bash regex. Example is for #2. The way this works is grep returns no output (thus a nonexistant string) if it doesn't match anything. So there are two tests, -z means "null string" and -n is "has data."

if [ -n "`echo $var1 | grep -e 'h.*o'`" ] ; then
  echo 'water found'
fi
1

[* is a regular command, similar to grep, find, or cat. You should be able to find it in /bin. Since it's a separate program, the shell will perform its normal set of expansions before handing [ its arguments.

As has been mentioned, since you're using * in your tests, you're getting glob expansions. Note that even if you use quotes, such as 'hel*', this probably won't work as you hope, because [ does not support patterns. In the case of h*o working, that is likely due to the presence of a file named hello in your current directory, and no other files matching that pattern. If it does work without a hello file, you may have an odd implementation, and your script is likely to fail on other systems.

Depending on your needs, there are a couple of options. Bash, Zsh, and some other shells have the [[ builtin. Since it is a builtin, it can give its arguments special treatment, including avoiding glob expansion. Additionally, it can do pattern matching. Try

var1=hello  
if [[ "$var1" = hel* ]]; then  
    echo success  
fi

Also, note the lack of quotes around the pattern. Without quotes, hel* is treated as a pattern by [[, with quotes (single or double), "hel*" is treated literally.

If you need wider compatibility, such as for shells without [[, you can use grep:

var1=hello
if echo "$var1" | grep -qe 'hel.*' ; then
    echo success
fi

No [ or [[ necessary here, but the quotes around 'hel.*' are.

*Some shells actually do have [ builtin, but this is for efficiency purposes. It should still behave identically to the separate executable, including having its arguments subjected to the shell's normal "mangling."

8bittree
  • 2,958
1

[[ is bash specific and using grep is unnecessarily slow as it needs to first start a new process. There is a way simpler, faster solution that works with every POSIX compatible shell:

var1=hello  
case $var1 in
    hel*) echo success
esac

More general the syntax is:

case $var in
    <pattern1>) action1 ; action2 ; action3 ; ... ; actionN ;;
    <pattern2>) action1 ; action2 ; action3 ; ... ; actionN ;;
    <pattern3>) action1 ; action2 ; action3 ; ... ; actionN ;;
    *) action1 ; action2 ; action2 ; ... ; actionN ;;
esac

The first pattern that matches defines which actions to perform. If you want else-actions that only run if no pattern matches, use * as pattern as that will always match. You terminate a set of actions with ;;, which are optional if the next instruction is esac anyway. Also you can use line breaks to separate instructions, so the following syntax is valid, too:

case $var in
    <pattern>) 
       action1
       action2
       action3
       :
       actionN
    ;;
*)
   action2
   action3
   :
   actionN

esac

And in patterns you can use * and ?, as well as | for "or" and [...], as in [abcd] (matches either of these 4 letters) or as in [A-Z] (matches all letters A to Z, as well as ! to negate the match. E.g.

for var in abc 123 3a b5 934 "" 
do
    case $var in
        "" | *[!0-9]*) echo "\"$var\" is NOT an integer!" ;;
        *) echo "\"$var\" is an integer."
    esac
done

Output:

"abc" is NOT an integer!
"123" is an integer.
"3a" is NOT an integer!
"b5" is NOT an integer!
"934" is an integer.
"" is NOT an integer!
Mecki
  • 1,218
0

How about this?

if [[ "$var1" == "hel"* ]]  

Give wildcard outside the quotes while doing a string comparison.

slm
  • 10,859