1

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?

smeep
  • 332
  • 1
  • 17
  • 1
    "zero-or-more-spaces + 'val2' + zero-or-more-spaces" is almost certainly not what you want, because `val21` would match this -- it's zero spaces on both sides, after all! I'm presuming that what you're *actually* interested in is "space-or-beginning + 'val2' + space-or-end". – Charles Duffy Aug 17 '16 at 02:11
  • ...also, in general, using a delimited string is the wrong way to represent a list of items in bash. If you were using an associative array keyed by individual values [as is appropriate for an unordered list], for instance, removing a value from it would be as simple as an `unset arr["val2"]`; for an ordered list you could do the same by index. String-munging in bash to maintain a list tends to be a code smell in and of itself, as it typically indicates poor choice of data structures. – Charles Duffy Aug 17 '16 at 02:56
  • You're right, I hadn't thought of a case where similar items could exist but with different beginning or endings. In my case, the values are given to me as a big space-delimited string, and represent IBM i compile parameters, such as "TGTRLS(V7R1M0) DBGVIEW(*ALL) DFTACTGRP(*NO)". They will always start with some letters, then a left parenthesis, then some value (which could start with an asterisk), then a right parenthesis. My goal with this script is to remove the parameters that match a set of default ones, so that the only ones remaining are outside of our defaults. – smeep Aug 17 '16 at 17:16

2 Answers2

4

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
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
3

Provided extglob shell option is enabled,

$ string="val1 val2 val3 val4"
$ v=val2
$ echo "${string/*( )$v*( )/ }"
val1 val3 val4
  • string/ is used to search and replace first occurrence of pattern. Use string// to replace all occurrences. See Parameter Expansion for further reading
  • *( ) means zero or more spaces. See Pattern Matching manual for more details and use of extglob option
  • The replace pattern is single space character
chepner
  • 497,756
  • 71
  • 530
  • 681
Sundeep
  • 23,246
  • 2
  • 28
  • 103
  • 1
    This does what the OP asked for, but I'm not sure it's what they actually *need*. `val21` would match with `v=val2` here, for instance. – Charles Duffy Aug 17 '16 at 02:14