Search code examples
bashshellvariable-expansionparameter-expansion

Can bash substring replacement use regexs?


Given a string such as

string="val1 val2 val3 val4"

how can you use bash substring replacement to remove a given substring and its adjoining space (which may or may not be present)?

For example, this results in extra spaces:

val='val2'
string=${string/$val/}
# string = "val1  val3 val4"

In my real-life code, I won't know in advance what the substring is or where it resides in the string, so it will be unknown if it has leading or trailing spaces. I wanted to do something like this, like you'd do in sed, but of course it didn't work:

val=" *val2 *"
string=${string/$val/ }
# In my fictitious universe, string = "val1 val3 val4"
# In the real world, string = "val1"

In sed, I would use something like sed -e 's/ *val2 */ /', but I'd like to do all this from within bash.

Is there a way to define the substring such that the pattern contains zero-or-more spaces + 'val2' + zero-or-more spaces?


Solution

  • Specification issues

    Consider as an initial state:

    v=val2
    string="val1 val21 val2 val3 val4"
    

    Implementing the precise behavior this question asks for will result in:

    string="val1 1 val3 val4"
    

    ...or, perhaps:

    string="val1 1 val2 val3 val4"
    

    I'm assuming in the below that what you really want as output in this case is:

    string="val1 val21 val3 val4"
    

    Approach: Posix Extended Regular Expressions / BASH_REMATCH

    This is more involved than strictly necessary (I would use the alternate approach shown below this for the immediate case at hand), but shows use of regexes for string replacement in native bash -- which can often be a useful technique.

    Consider using [[ $string =~ $re ]], which populates the array BASH_REMATCH with any groups from the regex re:

    string="val1 val2 val3 val4"
    val=val2
    
    if [[ $string =~ (.*(^|[[:space:]]))"$val"(($|[[:space:]]).*) ]]; then
      string="${BASH_REMATCH[1]}${BASH_REMATCH[3]}"
      string=${string//  / } # deal with any places where spaces are doubled up
    fi
    

    Pattern replacement with temporary padding

    Unconditionally prepending and suffixing your string with spaces means you can use identical replacement logic, with no regex-like conditionals, with your values to be removed located anywhere in the string:

    string="val1 val2 val3 val4"
    val=val2
    
    s=" $string "       # Unconditionally add leading and trailing spaces
    s=${s// $val / }    # Substitute the value only when surrounded by space
    s=${s# }; s=${s% }  # Trim leading and trailing spaces back off
    
    string=$s