2

I've got a shell script variable, which currently is defined like this:

tsource=/backup/%HOST%/%SHARE%/%PERIOD%

I want to match this template to another variable, which might have a value like this:

psource=/backup/somehost/someshare/monthly.1

My aim is to generate the following assignments so that I can use them for a substitution later in the script:

vars[HOST]=somehost
vars[SHARE]=someshare
vars[PERIOD]=monthly.1

I should point out that the user can override both these values and we might end up with different (but still matching) shapes, which means that a simple "split at /" (or even "split at punctuation") is insufficient:

tsource=/backup/%PERIOD/where/%HOST%-%SHARE%
psource=/backup/monthly.1/where/somehost-someshare

The intention is to be able to parse $psource based on the template provided by $tsource. As it's utility software I don't really care if the user tries to break it - it'll bail at a later point if insufficient or invalid parameters have been provided.

Looking at possible solutions I think something like sscanf could be useful here, which is a tool that's indirectly available to me. I can easily enough manipulate $tsource to extract HOST, SHARE, and PERIOD, and derive a sscanf template:

grep -Po "(?<=%)[[:upper:]]+(?=%)" <<<"$tsource"           # "HOST" "SHARE" "PERIOD"
tscanf=$(sed -re 's/%[[:upper:]]+%/%s/g' <<<"$tsource")    # "/backup/%s/%s/%s"

This would allow me to apply the template in Perl, like this example:

perl -MString::Scanf -e '
    $psource = "/backup/somehost/someshare/monthly.1";
    $tscanf = "/backup/%s/%s/%s";
    ($host, $source, $period) = sscanf($tscanf, $psource);
    print "host=$host, share=$share, period=$period\n"
'
# "host=somehost, share=someshare, period=monthly.1"

(If I was going to dive into Perl I'd probably do the template rewriting and sscanf generation in the Perl part too, but let's park that for now.)

However, it grates somewhat writing a shell script that needs perl.

Is there an alternative (better) solution that allows me to map values from a string for fairly arbitrary labels in a template, that doesn't involve a quick dive into Perl?

2
  • Why not simply use something like IFS='/' read -ra vars <<< $(echo ${psource#/*/})?; this'll give you your values in the array vars: vars[0] would be: somehost, and so on Commented Jun 17, 2019 at 23:22
  • @matsib.dev thanks. I've updated my question with a second example to show why that won't work. I've also commented under your answer. Commented Jun 18, 2019 at 8:58

2 Answers 2

3

Is using regular expressions an option? I still had to shell out to sed to cleanly replace %...% with (.*), though:

$ psource=/backup/monthly.1/where/somehost-someshare
$ tsource=/backup/%PERIOD%/where/%HOST%-%SHARE%
$ # replace labels with '(.*)', need sed to avoid greediness of * in ${../../..}
$ tsource_re=$(sed 's/%[^%]*%/(.*)/g' <<<"${tsource}")
$ # match against tsource to get template names
$ [[ $tsource =~ $tsource_re ]]; tmatch=(${BASH_REMATCH[@]:1})
$ # match against psource to get template values
$ [[ $psource =~ $tsource_re ]]; pmatch=(${BASH_REMATCH[@]:1})
$ for i in ${!tmatch[@]}; do printf "%s:\t%s\n" "${tmatch[$i]}" "${pmatch[$i]}"; done
%PERIOD%:   monthly.1
%HOST%: somehost
%SHARE%:    someshare
1
  • That looks like it does the job, thank you very much. The only significant change is that I've created a psource_re=$(sed 's/%[^%]*%/(.*)/g' <<<"${tsource}") for matching against the $psource value so that the percent symbols can be discarded. Commented Jun 18, 2019 at 11:13
1

this should do the trick:

tsource='/backup/%HOST%/%SHARE%/%PERIOD%'
psource='/backup/somehost/someshare/monthly.1'
IFS=$'\n' read -d '' -ra var_names <<< $(grep -Po "(?<=%)[[:upper:]]+(?=%)" <<< "$tsource")
IFS='/' read -ra var_values <<< $(echo ${psource#/*/})
declare -A vars=( ["${var_names[0]}"]="${var_values[0]}" ["${var_names[1]}"]="${var_values[1]}" ["${var_names[2]}"]="${var_values[2]}" )

echo ${vars[HOST]} ${vars[SHARE]} ${vars[PERIOD]}

> somehost someshare monthly.1

1
  • 1
    Thank you matsib.dev. I'm going to +1 this because it does address the specific example I quoted. Unfortunately it doesn't handle my general case (which is why I described the scheme as templating) so I can't accept it as an answer. I've updated my question to include a second example. Commented Jun 18, 2019 at 8:56

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.