Parameter Expansion
Every time I would sit down to write a shell script and pull out the usual slicing and dicing utilities like sed and cut I would be reminded that shells were more sophisticated now and there were ways to do minor tweaking to strings without anything more than built-in variable expansion.
That being said, I didn’t know how to do it and short of some copy and paste I had never worked out how it was done. But I had some time so I figured today was going to be the day.
After googling around a bit to figure out what it was even called, I wound up on the Bash documentation page.
https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html
I don’t know if it’s a learning style, or being impatient, or ADD, or some combo of all of the above, but I struggle to read technical documentation. I feel like Alex Baldwin in Beetlejuice trying to make sense of the Handbook for the Recently Deceased. 1
The problem of the day was that I had a path and I needed to access the first
slash-delimited portion and then everything after that point. So for example, I
have path/to/somewhere
and I want path
and to/somewhere
. My first thought
was to use cut because that’s how I was raised: take whatever string you’re
starting with and then beat it with grep
, cut
, sed
, and sometimes awk
until it looks the way you want.
$ var="path/to/somewhere"
$ echo $var | cut -d'/' -f1
path
$ echo $var | cut -d'/' -f2-
to/somewhere
That would do the trick. And then I remembered there dirname
and basename
but I was 87% sure that wasn’t what I wanted. I was pretty sure it was for paths
$ var="path/to/somewhere"
$ dirname $var # I mean cool, but not helpful
path/to
$ basename $var # no good here either
somewhere
# let's add a file type to play with the -s option
$ var="path/to/somewhere.jpg"
$ basename -s .jpg $var # kinda cool; it takes the suffix with it
somewhere
Those are most definitely not going to help and even if it did, it puts me back in the basket with beating variables with shell utilities.
So back to the Bash documentation. I skimmed through it looking for examples
because that’s just how my brain absorbs information. The only examples in here
are for subscripting strings and arrays. That’s a little frustrating because I’m pretty sure that what I want is
down below ${parameter#word}
and ${parameter%word}
and its friends.
But I need examples!
After much googling I landed on this page and it was lovely:
https://www.cyberciti.biz/tips/bash-shell-parameter-substitution-2.html
Looking through the examples, the one I wanted for the first case where I only
wanted the path
portion of path/to/somewhere
was explained here:
https://www.cyberciti.biz/tips/…Remove%20Pattern%20(Back%20of%20%24VAR).
In the example they’re going from xcache-1.3.0.tar.gz
to xcache-1.3.0
but I
can use the same thinking to get the end of my string by using a *
in place
of the suffix like this:
$ var="path/to/somewhere"
$ echo "${var%%/*}"
somewhere
And then to work on the end of the string, the process was explained here:
https://www.cyberciti.biz/tips/…Remove%20Pattern%20(Front%20of%20%24VAR)
$ var="path/to/somewhere"
$ echo "${var#*/}"
path/to
That solved that problem, but of course later in the same morning I had another
need to do some slicing. This time it was staticcheck
output:
internal/foo/bar/app_test.go:14:10: config.FunctionA is deprecated: Please use FunctionB to properly log. (SA1019)
I wanted this portion so I could feed it to my favorite editor to start working on the problem:
internal/foo/bar/app_test.go:14:10
If I were going back to my hack and slash routine, it’d be a split on the space
with cut
, taking the first token, and removing the last character with sed
.
$ var="internal/foo/bar/app_test.go:14:10: config.FunctionA is deprecated: Please use FunctionB to properly log. (SA1019)"
$ echo $var | cut -d' ' -f1 | sed 's/.$//'
internal/foo/bar/app_test.go:14:10
Time to flex our new skills!
$ var="internal/foo/bar/app_test.go:14:10: config.FunctionA is deprecated: Please use FunctionB to properly log. (SA1019)"
$ echo ${var%% *} # delete from the right to the earliest space
internal/foo/bar/app_test.go:14:10:
$ newvar=${var%% *} # let's save a copy to further whittle down
$ echo ${newvar%%:} # delete the colon from the right
internal/foo/bar/app_test.go:14:10
I could do it in two steps, but I wondered if chaining them was possible like
you can do with sed
(e.g., `sed ’s/foo/bar/;s/bing/baz/'). A little googling
came up with nothing for that, so I decided to try and nest them.
$ var="internal/foo/bar/app_test.go:14:10: config.FunctionA is deprecated: Please use FunctionB to properly log. (SA1019)"
$ echo "${${var%% *}%%:}"
internal/foo/bar/app_test.go:14:10
Very nice! I’ll take it!
-
A detour to learn how to include YouTube timecodes which required me to learn how to create a custom shortcode and finally how to set up footnotes. ↩︎