These things pops up from time to time, manipulating variables and strings using bash. There are a few very handy variable operators that can reliably detect whether strings are set or not, strip of tails or heads, and even do some basic replacements.
Checking whether a variable is set (or not)
This can be done with the + operator. Essentially:
$ echo ${var+isset}
$ var=foobar
$ echo ${var+isset}
isset
$
So if var is set, then ${var+isset} will result in isset, otherwise the empty string. The – operator does exactly the opposite, it returns the empty string if var is set, or the text after – if it’s not set. It’s not possible to combine the two simply, so instead you need something like “${var-noset}${var+isset}” which is clunky, but it’ll work.
This is handy if you need to know whether a variable is set or not (remember that the empty string is still set, but [ -z “${var}” ] as a simple check will return false):
[[ -n “${var+y}” ]] && action if set || action if not set
Stripping off extensions
Need to get rid of that last little bit? Eg, have a filename like foo.bar in a variable but need only foo? Well:
$ var=foo.bar
$ echo ${var%.bar}
foo
$
Don’t know what the extension is and still need to strip it off? Well, glob it:
$ echo ${var%.*}
foo
$
And this is handled properly with strings containing multiple dots as well:
$ var=foo.bar.ext
$ echo ${var%.*}
foo.bar
$
Do happen to need to get rid of everything after the first dot? Well, just double up on the % and make it greedy:
$ echo ${var%%.*}
foo
$
And what about heads
Whilst % gets rid of the tail # gets rid of the head:
$ var=foo.bar.ext
$ echo ${var#foo}
.bar.ext
$ echo ${var#*.}
bar.ext
$ echo ${var##*.}
ext
$
Simple string replacement
Whilst the syntax reminds one of sed, it should not be confused:
$ var=foo.bar.ext
$ echo ${var/bar/asdf}
foo.asdf.ext
$ echo ${var/*bar/asdf}
asdf.ext
$ echo ${var/bar*/asdf}
foo.asdf
$
It’s not regular expressions, but rather globs. There is (afaik) no concept of memory. This one is all gready, I don’t seem to be able to locate a non-greedy variant, various guesses has come up empty-handed:
$ var=aaabbbccc
$ echo ${var/ab*c/__}
aa__
$ echo ${var//ab*c/__}
aa__
$ echo ${var/ab*c//__}
aa/__
$ echo ${var/ab*c/__/}
aa__/
$
Still extremely handy.
Some more useful ones.
Substring extraction with “:”
$ var=aaabbbccc
$ echo ${var:5}
bccc
$ echo ${var:2:4}
abbb
A quick and easy len(var):
$ echo ${#var}
9
Unfortunately the string replacement trick is always greedy (by definition). For
${parameter/pattern/string}
The man page says that parameter is expanded and the *longest* match with pattern is replaced with string. It also says that parameter expansion is performed just like pathname expansion (globs).
Globs are always greedy. (Can you just imagine the frantic queries from users if they weren’t?)
I actually just remembered another one:
$ somearbvarname=content
$ varname=somearbvarname
$ echo ${!varname}
content
$
Sometimes handy if you have lists of things, for example:
$ net_server1=192.168.0.1
$ net_server2=192.168.1.0/24
$ net_server3=192.168.2.0/24
And now you get a parameter like “server1” or “server2”, now it would have been nice to be able to do something like:
$ echo ${net_${1}}
However, that gives you errors about bad substitutions, so instead, we use a two liner:
$ netvar=net_${sname}
$ echo ${!netvar}
192.168.1.0/24
$
The #var trick also works for arrays:
$ arvar=(aa bb cc dd ee ff)
$ echo ${#arvar[@]}
6
$
And then of course there is the parameter count hack:
$ function foo() {
echo “Param count: $#”
for ((i=1;i<=$#;++i)); do echo "Param $i: ${!i}" done } $ foo a b c Param count: 3 Param 1: a Param 2: b Param 3: c $ foo a b "" c Param count: 4 Param 1: a Param 2: b Param 3: Param 4: c $ Which also abuses the previous ! indirection hack. Use this only if using "shift" doesn't make sense.
Non-greedy string replacement using Bash parameter expansion is possible, at least indirectly:
var=aaabbbccc
s1=${var%%b*}
idx=${#s1}
s2=${var#${s1}}
s3=”${s2%%[^b]*}”
len=${#s3}
echo “${var/${var:${idx}:${len}}/__}”
echo “${var/a${var:${idx}:${len}}c/__}”