Search code examples
bashshellvariablesrefactoringsimplify

What is the correct syntax to combine multiple parameter expansions?


My current code:

while read -r rbv_line || [[ -n "$rbv_line" ]]; do
  if [[ "${rbv_line}" =~ ${rbv_reg} ]]; then
    rbv_downcase="${BASH_REMATCH[0],,}" &&
      ruby_version="${rbv_downcase//[^0-9a-z\.\-]/}" &&
      ((reg_matches="${reg_matches}"+1))
    printf "\n"
    printf "Setting Ruby version: %s\n" "${ruby_version}"
    break
  fi
done < "${1}" 

It does what I want. But I would love to know if I can simplify this code even more, hoping someone can help me understand the syntax.

If you see these two lines:

rbv_downcase="${BASH_REMATCH[0],,}" &&
  ruby_version="${rbv_downcase//[^0-9a-z\.\-]/}" &&

Initially I tried to combine those into one using something like this:

ruby_version="${BASH_REMATCH[0],,//[^0-9a-z\.\-]/}"

That does not work.

Is there a way to combine those two parameter expansions (,, and the //[^0-9a-z\.\-]/) or is passing it through an intermediary variable the right approach?

You can view the latest version of the code here: https://github.com/octopusnz/scripts


Solution

  • You cannot combine multiple parameter expansions, but...


    ... you can simplify this code!

    The biggest gain is by using already available tools.

    1. Instead of looping, let's use grep. It's supposed to do something when RegEx pattern is occurred, so:
    grep -E "$rbv_reg" "$1" # -E is for extended RegEx
    
    1. I guess your pattern isn't case sensitive, so let's disable it with -i flag.
    2. The loop breaks after match, so let's pass -m 1 to stop processing file after first match.
    3. You want to convert uppercase to lowercase, so let's pipe it through tr:
    grep -m 1 -E -i "$rbv_reg" "$1" | tr '[:upper:]' '[:lower:]'
    
    1. You then replace some characters with //[^0-9a-z\.\-]/, piping it to sed will do the trick:
    grep -m 1 -E -i "$rbv_reg" "$1" | tr '[:upper:]' '[:lower:]' | sed 's/[^0-9a-z\.\-]//g'
    
    1. And at the very end, let's grab the output to variable:
    ruby_version="$( grep -m 1 -E -i '$rbv_reg' '$1' | tr '[:upper:]' '[:lower:]' | sed 's/[^0-9a-z\.\-]//g' )"
    
    1. Since you are printing new line anyway, let's use simple echo instead of printf
    2. All what's left is if [ -n "$ruby_version" ] to increment reg_matches

    At the end, we got:

    ruby_version="$(
        grep -m 1 -E -i '$rbv_reg' '$1' |
        tr '[:upper:]' '[:lower:]' |
        sed 's/[^0-9a-z\.\-]//g'
    )"
    
    if [ -n "$ruby_version" ]; then
        reg_matches="$((reg_matches+1))"
        echo
        echo "Setting Ruby version: $ruby_version"
    fi
    

    The advantage of above code is the fact it isn't really Bash dependent and should work in any POSIX Bourne compatible shell.