Check If a String Contains a Substring in Bash
This can be handy to do something different based on a condition which helps solve any number of business use cases.
Prefer video? Here it is on YouTube.
In my case I was modifying a release script to support the idea of being able to deploy to multiple environments in a CI pipeline and I wanted to be able to easily skip deploying to an environment by setting an environment variable.
The use case here is you may want to lock your app(s) to a specific release for a few week long penetration test but still deploy the latest version to production. However, you can apply this to anything like demo, staging or whatever environments you might deploy to.
I landed on the idea of having DEPLOYABLE_ENVS=prod
which also supports
DEPLOYABLE_ENVS=pen,prod
and the idea is you can call your release script
multiple times in a pipeline for a specific environment such as: mkrelease pen ...
or mkrelease prod ...
Then it’s a matter of exiting early in your release script if the environment
(pen
) doesn’t exist in DEPLOYABLE_ENVS
. Super simple but very effective.
It’s a matter of searching a string for another string, AKA. checking if a
string contains a substring.
It worked well because it doesn’t require a code or pipeline file change to control which environments get deployed to. You only have to adjust a single environment variable in your CI pipeline’s settings. It also supports releases happening automatically after code is merged to your main branch as opposed to manually running a custom pipeline.
# The Script
Here’s a standalone demo
script that shows how it works, it works with Bash
3.2+. Since we’re using a *
wildcard in a condition it does require [[ ]]
instead of [ ]
so that means needing a Bash compatible shell, this is not
POSIX compliant:
#!/usr/bin/env bash
set -o errexit
set -o pipefail
set -o nounset
ENV="${1:-prod}"
DEPLOYABLE_ENVS="${DEPLOYABLE_ENVS:-pen,prod}"
# This is the condition. We're using * to match anything on either side of the
# string. Of course you can adjust this as needed for your use case. It's
# important that the wildcard is on the right side of the condition.
if [[ "${DEPLOYABLE_ENVS}" == *"${ENV}"* ]]; then
echo "Yes"
else
echo "No"
exit
fi
# The rest of the script.
If you don’t plan to have an else
condition you can also negate the condition
with !=
.
What about a Regex?
You might be thinking “why not use a regex with =~ .*"${ENV}".*
instead? You
could but I think the wildcard approach is more readable for this use case. I’d
reach for using a regex if I needed more advanced matching.
Also a wildcard match is ~3.5 faster (at least on my machine) but keep in mind for normal usage this probably won’t have a big impact:
$ time for x in {1..1000000}; do [[ pen,prod == *prod* ]]; done
real 0m2.482s
user 0m2.422s
sys 0m0.060s
$ time for x in {1..1000000}; do [[ pen,prod =~ .*prod.* ]]; done
real 0m8.793s
user 0m8.793s
sys 0m0.000s
Here’s a couple of outputs with the original script:
# Since ENV defaults to prod, this is expected
./demo
Yes
./demo pen
Yes
./demo prod
Yes
# This environment isn't in DEPLOYABLE_ENVS so it gets skipped
./demo staging
No
The video below shows how it works.
# Demo Video
Timestamps
- 0:33 – Solving a real business use case
- 2:18 – Looking at the script
References
What use case did you end up doing this for? Let me know below.