1

I need to replace one variable with another variable in a multiple strings. For example:

string1="One,two" 
string2="three.four" 
string3="five:six"
y=";"
for str in string1 string2 string3; do
    x="$(echo "$str" | sed 's/[a-zA-Z]//g')" # extracting a character between letters
    sed 's/$x/$y/'$str # I tried this, but it does not work at all.
    echo "$str"
done

Expecting output:

One;two
three;four
five;six

In my output, nothing changes:

One,two
three.four
five:six
mck
  • 53
  • 6
  • what's the issue? please update the question with a) the (wrong) output generated by your code and b) the (correct) expected output – markp-fuso Jun 23 '22 at 00:34
  • There's an extra `)` on the `string3` line. – Barmar Jun 23 '22 at 00:44
  • 1
    Use the bash `${variable//old/new}` parameter expansion syntax rather than `sed`. – Barmar Jun 23 '22 at 00:45
  • The second argument to `sed` is a filename, not a string. – Barmar Jun 23 '22 at 00:45
  • @Barmar I tried running `${str//$x/$y}` and `${str//"$x"/"$y"}` and it does not work. – mck Jun 23 '22 at 00:58
  • @Barmar Also I found the answer to similiar question with `sed` - changing `.` to `,` with numbers and I thought that it may also work with variables instead of constant value characters. – mck Jun 23 '22 at 01:00
  • You're missing the `$` before variables in `for str in string1 string2 string3` – Barmar Jun 23 '22 at 01:02
  • Using shell variables in `sed` substitutions is very tricky, and best avoided. See [Is it possible to escape regex metacharacters reliably with sed](https://stackoverflow.com/q/29613304/4154375). – pjh Jun 23 '22 at 01:21

2 Answers2

2

You can use bash's substitution operator instead of sed. And simply replace anything that isn't a letter with $y.

#!/bin/bash

string1="One,two"
string2="three.four"
string3="five:six"
y=";"

for str in "$string1" "$string2" "$string3"; do
    x=${str//[^a-zA-Z]+/$y}
    echo "$x"
done

Output is:

One;two
three;four
five;six

Note that your general approach wouldn't work if the input string has muliple delimiters, e.g. One,two,three. When you remove all the letters you get ,,, but that doesn't appear anywhere in the string.

Barmar
  • 741,623
  • 53
  • 500
  • 612
1

Addressing issues with OP's current code:

  • referencing variables requires a leading $, preferably a pair of {}, and (usually) double quotes (eg, to insure embedded spaces are considered as part of the variable's value)
  • sed can take as input a) a stream of text on stdin, b) a file, c) process substitution or d) a here-document/here-string
  • when building a sed script that includes variable refences the sed script must be wrapped in double quotes (not single quotes)

Pulling all of this into OP's current code we get:

string1="One,two" 
string2="three.four" 
string3="five:six"
y=";"

for str in "${string1}" "${string2}" "${string3}"; do   # proper references of the 3x "stringX" variables
    x="$(echo "$str" | sed 's/[a-zA-Z]//g')"
    sed "s/$x/$y/" <<< "${str}"                         # feeding "str" as here-string to sed; allowing variables "x/y" to be expanded in the sed script
    echo "$str"
done

This generates:

One;two                 # generated by the 2nd sed call
One,two                 # generated by the echo
;hree.four              # generated by the 2nd sed call
three.four              # generated by the echo
five;six                # generated by the 2nd sed call
five:six                # generated by the echo

OK, so we're now getting some output but there are obviously some issues:

  • the results of the 2nd sed call are being sent to stdout/terminal as opposed to being captured in a variable (presumably the str variable - per the follow-on echo ???)
  • for string2 we find that x=. which when plugged into the 2nd sed call becomes sed "s/./;/"; from here the . matches the first character it finds which in this case is the 1st t in string2, so the output becomes ;hree.four (and the . is not replaced)
  • dynamically building sed scripts without knowing what's in x (and y) becomes tricky without some additional coding; instead it's typically easier to use parameter substitution to perform the replacements for us
  • in this particular case we can replace both sed calls with a single parameter substitution (which also eliminates the expensive overhead of two subprocesses for the $(echo ... | sed ...) call)

Making a few changes to OP's current code we can try:

string1="One,two" 
string2="three.four" 
string3="five:six"
y=";"

for str in "${string1}" "${string2}" "${string3}"; do
    x="${str//[^a-zA-Z]/${y}}"                          # parameter substitution; replace everything *but* a letter with the contents of variable "y"
    echo "${str} => ${x}"                               # display old and new strings
done

This generates:

One,two => One;two
three.four => three;four
five:six => five;six
markp-fuso
  • 28,790
  • 4
  • 16
  • 36
  • 1
    "preferably a pair of {}" Where have you heard this preference, I almost never see the `{}` unless the variable followed by alphanumerics and you need to delimit it. – Barmar Jun 23 '22 at 01:12
  • Thank you for a thorough answer and clarification of all the errors. – mck Jun 23 '22 at 01:15
  • 2
    I think most people find `"$foo"` just as readable as `"${foo}"` – Barmar Jun 23 '22 at 01:19