Unix & Linux: Test if a string contains a substring (4 Solutions!!)
Channel: Roel Van de Paar
Date: 08/08/2020

Unix & Linux: Test if a string contains a substring


The Question: I have the code
file="JetConst_reco_allconst_4j2t.png"
if [[ $file == *_gen_* ]];
then
 echo "True"
else
 echo "False"
fi
I test if file contains "gen". The output is "False". Nice!
The problem is when I substitute "gen" with a variable testseq:
file="JetConst_reco_allconst_4j2t.png"
testseq="gen"
if [[ $file == *_$testseq_* ]];
then
 echo "True"
else
 echo "False"
fi
Now the output is "True". How could it be? How to fix the problem?

Solutions: Please watch the whole video to see all solutions, in order of how many people found them helpful

== This solution helped 28 people ==
You need to interpolate the $testseq variable with one of the following ways:
 * $file == *_"$testseq"_* (here $testseq considered as a fixed string)
 * $file == *_${testseq}_* (here $testseq considered as a pattern).
Or the _ immediately after the variable's name will be taken as part of the
variable's name (it's a valid character in a variable name).

== This solution helped 22 people ==
file="JetConst_reco_allconst_4j2t.png"
testseq="gen"

case "$file" in
*_"$testseq"_*) echo 'True' ;;
*) echo 'False'
esac
Using case ... esac is one of the simplest ways to perform a pattern match in a
portable way. It works as a "switch" statement in other languages (bash, zsh,
and ksh93 also allows you to do fall-through in various incompatible ways). The
patterns used are the standard file name globbing patterns.
The issue you are having is due to the fact that _ is a valid character in a
variable name. The shell will thus see *_$testseq_* as "*_ followed by the
value of the variable $testseq_ and an *". The variable $testseq_ is undefined,
so it will be expanded to an empty string, and you end up with *_*, which
obviously matches the $file value that you have. You may expect to get True as
long as the filename in $file contains at least one underscore.
To properly delimit the name of the variable, use "..." around the expansion:
*_"$testseq"_*. This would use the value of the variable as a string. Would you
want to use the value of the variable as a pattern, use *_${testseq}_* instead.
Another quick fix is to include the underscores in the value of $testseq:
testseq="_gen_"
and then just use *"$testseq"* as the pattern (for a string comparison).

== This solution helped 33 people ==
Use the =~ operator to make regular expression comparisons:
#!/bin/bash
file="JetConst_reco_allconst_4j2t.png"
testseq="gen"
if [[ $file =~ $testseq ]];
then
echo "True"
else
echo "False"
fi
This way, it will compare if $file has $testseq on its contents.
user@host:~$ ./string.sh
False
If I change testseq="Const":
user@host:~$ ./string.sh
True
But, be careful with what you feed $testseq with. If the string on it somehow
represents a regex (like [0-9] for example), there is a higher chance to
trigger a "match".
Reference:
 * http://tldp.org/LDP/abs/html/bashver3.html#REGEXMATCHREF

With thanks & praise to God, and with thanks to the many people who have made this project possible! | Content (except music & images) licensed under cc by-sa 3.0 | Music: https://www.bensound.com/royalty-free-music | Images: https://stocksnap.io/license & others | With thanks to user Zebiano (https://unix.stackexchange.com/users/252652), user Viesturs (https://unix.stackexchange.com/users/226971), user RomanPerekhrest (https://unix.stackexchange.com/users/148009), user Kusalananda (https://unix.stackexchange.com/users/116858), user Isaac (https://unix.stackexchange.com/users/232326), and the Stack Exchange Network (http://unix.stackexchange.com/questions/370889). Trademarks are property of their respective owners. Disclaimer: All information is provided "AS IS" without warranty of any kind. You are responsible for your own actions. Please contact me if anything is amiss at Roel D.OT VandePaar A.T gmail.com.