Rob H
Rob H

Reputation: 15000

sed substitution with user-specified replacement string

The general form of the substitution command in sed is:

s/regexp/replacement/flags

where the '/' characters may be uniformly replaced by any other single character. But how do you choose this separator character when the replacement string is being fed in by an environment variable and might contain any printable character? Is there a straightforward way to escape the separator character in the variable using bash?

The values are coming from trusted administrators so security is not my main concern. (In other words, please don't answer with: "Never do this!") Nevertheless, I can't predict what characters will need to appear in the replacement string.

Upvotes: 3

Views: 291

Answers (4)

kyb
kyb

Reputation: 8181

From man sed

\cregexpc  
          Match lines matching the regular expression regexp.  The  c  may  be  any
          character.

When working with paths i often use # as separator:

sed s\#find/path#replace/path#

No need to escape / with ugly \/.

Upvotes: 0

NeronLeVelu
NeronLeVelu

Reputation: 10039

2 options:

1) take a char not in the string (need a pre process on content check and possible char without warranty that a char is available)

# Quick and dirty sample using `'/_#@|!%=:;,-` arbitrary sequence

Separator="$( printf "%sa%s%s" '/_#@|!%=:;,-' "${regexp}" "${replacement}" \
 | sed -n ':cycle
     s/\(.\)\(.*a.*\1.*\)\1/\1\2/g;t cycle
     s/\(.\)\(.*a.*\)\1/\2/g;t cycle
     s/^\(.\).*a.*/\1/p
    ' )"
echo "Separator: [ ${Separator} ]"
sed "s${Separator}${regexp}${Separator}${replacement}${Separator}flag" YourFile

2) escape the wanted char in the string patterns (need a pre process to escape char).

# Quick and dirty sample using # arbitrary with few escape security check
regexpEsc="$( printf "%s" "${regexp}" | sed 's/#/\\#/g' )"
replacementEsc"$( printf "%s" "${replacement}" | sed 's/#/\\#/g' )"
sed 's#regexpEsc#replacementEsc#flags' YourFile

Upvotes: 0

clt60
clt60

Reputation: 63952

Here isn't (easy) solution for the following using the sed.

while read -r string from to wanted
do
    echo "in [$string] want replace [$from] to [$to]  wanted result: [$wanted]"
    final=$(echo "$string" | sed "s/$from/$to/")
    [[ "$final" == "$wanted" ]] && echo OK || echo WRONG
    echo
done <<EOF
=xxx= xxx === =====
=abc= abc /// =///=
=///= /// abc =abc=
EOF

what prints

in [=xxx=] want replace [xxx] to [===]  wanted result: [=====]
OK

in [=abc=] want replace [abc] to [///]  wanted result: [=///=]
sed: 1: "s/abc/////": bad flag in substitute command: '/'
WRONG

in [=///=] want replace [///] to [abc]  wanted result: [=abc=]
sed: 1: "s/////abc/": bad flag in substitute command: '/'
WRONG

Can't resists: Never do this! (with sed). :)

Is there a straightforward way to escape the separator character in the variable using bash?

No, because you passing the strings from variables, you can't easily escape the separator character, because in "s/$from/$to/" the separator can appear not only in the $to part but in the $from part too. E.g. when you escape the separator it in the $from part it will not do the replacement at all, because will not find the $from.

Solution: use something other as sed

1.) Using pure bash. In the above script instead of the sed use the

    final=${string//$from/$to}

2.) If the bash's substitutions are not enough, use something to what you can pass the $from and $to as variables.

  • as @anubhava already said, can use: awk -v f="$from" -v t="$to" '{gsub(f, t)} 1' file

  • or you can use perl and passing values as environment variables

final=$(echo "$string" | perl_from="$from" perl_to="$to" perl -pe 's/$ENV{perl_from}/$ENV{perl_to}/')
  • or passing the variables to perl via the command line arguments
final=$(echo "$string" | perl -spe 's/$f/$t/' -- -f="$from" -t="$to")

Upvotes: 1

anubhava
anubhava

Reputation: 785481

You can use control character as regex delimiters also like this:

s^Aregexp^Areplacement^Ag

Where ^A is CTRLva pressed together.

Or else use awk and don't worry about delimiters:

awk -v s="search" -v r="replacement" '{gsub(s, r)} 1' file

Upvotes: 1

Related Questions