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.

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

$ echo $var | cut -d'/' -f2-

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

$ basename $var  # no good here either

# 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

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:

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:…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%%/*}"

And then to work on the end of the string, the process was explained here:…Remove%20Pattern%20(Front%20of%20%24VAR)

$ var="path/to/somewhere"
$ echo "${var#*/}"

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:


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/.$//'

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

$ newvar=${var%% *}  # let's save a copy to further whittle down

$ echo ${newvar%%:}  # delete the colon from the right

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%% *}%%:}"

Very nice! I’ll take it!

  1. 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↩︎