165

How do you pass a variable containing slashes as a pattern to sed?

For example, if I have the following variable:

var="/Users/Documents/name/file"

I want to pass it to sed as so:

sed "s/$var/replace/g" "$file"

However I get errors. How can I circumvent the issue?

Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563
buydadip
  • 8,890
  • 22
  • 79
  • 154
  • 1
    See also https://stackoverflow.com/questions/5864146/using-different-delimiters-in-sed-commands-and-range-addresses – tripleee Mar 13 '22 at 08:02

7 Answers7

292

Use an alternate regex delimiter as sed allows you to use any delimiter (including control characters):

sed "s~$var~replace~g" $file

As mentioned above we can use control character as delimiter as well like:

sed "s^[$var^[replace^[g" file

Where ^[ is typed by pressing Ctrl-V-3 together.

Or else in bash shell you can use this sed with \03 as delimiter:

d=$'\03'
sed "s${d}$var${d}replace$d" file
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • 5
    Doesn't work. I try sed -i "s~blah~$var~g file" and get: "sed -e expression #1, char 70: unterminated `s' command". I have confirmed that the culprit is a slash in $var. Variants of this solution to this are all over the place and none of them work. Was sed recently updated? I'm on v4.2.2 on Ubuntu 14.04.4 LTS. – Paul Ericson Sep 01 '16 at 00:41
  • 2
    That depends on what is value of `$var` – anubhava Sep 01 '16 at 07:39
  • 3
    Doesn't work with ~ as @PaulEricson observed, but still works with | – Kenny Ho Nov 04 '16 at 21:15
  • don't work for me. I get ```sed: -e expression #1, char 17: unknown option to `s'``` (https://stackoverflow.com/a/27788661/860878 worked) – pablorsk Jun 20 '17 at 13:31
  • 2
    The last "~" (after g) seems not to be needed, at least for AWS Amazon Linux (CentOS based) – Saúl Martínez Vidals Jan 22 '18 at 05:44
95

A pure bash answer: use parameter expansion to backslash-escape any slashes in the variable:

var="/Users/Documents/name/file"
sed "s/${var//\//\\/}/replace/g" $file
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
  • 2
    This is a good way, because you don't always know what characters contains the variable. So you can always escape the sed delimiter if there is a doubt.For instance : sed "s:${var//:/\\:}:replace:g" $file – Stephane L May 11 '17 at 10:25
  • Beautiful. Made some renaming scripts of mine much cleaner. – Cloud Nov 29 '17 at 13:33
  • 1
    Learned something beautiful today, thank you. Should be the accepted answer. – Steve Kehlet May 26 '18 at 01:09
  • 15
    Some clarity on the pattern being used here: `${parameter/pattern/string}`. So, in this case, the parameter is `var`, the pattern is `/\/`, and the string is `\\/`. All instances of the pattern are replaced because the pattern begins with a `/`. – Josh Aug 07 '18 at 22:17
  • 2
    @josh, and so the leading slash of the pattern is not actually part of the pattern to replace. – glenn jackman Aug 07 '18 at 23:33
  • 2
    @glennjackman true, though in the documentation you reference they say "If pattern begins with ‘/’, all matches of pattern are replaced with string." I was following their convention as talking about it as part of the pattern. It does seem an awkward way to talk about it though but the syntax is pretty awkward to begin with in my opinion. – Josh Aug 08 '18 at 03:10
42

Another way of doing it, although uglier than anubhava's answer, is by escaping all the backslashes in var using another sed command:

var=$(echo "$var" | sed 's/\//\\\//g')

then, this will work:

sed "s/$var/replace/g" $file
buydadip
  • 8,890
  • 22
  • 79
  • 154
16

Using / in sed as a delimiter will conflict with the slashes in the variable when substituted and as a result you will get an error. One way to circumvent this is to use another delimiter that is unique from any characters that is in that variable.

var="/Users/Documents/name/file"

you can use the octothorpe character which suits the occasion (or any other character that is not a / for ease of use)

sed "s#$var#replace#g" 

or

sed 's#$'$var'#replace#g'

this is suitable when the variable does not contain spaces

or

sed 's#$"'$var'"#replace#g'

It is wiser to use the above since we are interested in substituting whatever is in that variable only as compared to double quoting the whole command which can cause your shell to interpet any character that might be considered a special shell character to the shell.

pkamb
  • 33,281
  • 23
  • 160
  • 191
repzero
  • 8,254
  • 2
  • 18
  • 40
  • Doesn't work. I try sed -i "s~blah~$var~g file" and get: "sed -e expression #1, char 70: unterminated `s' command". I have confirmed that the culprit is a slash in $var. Variants of this solution to this are all over the place and none of them work. Was sed recently updated? I'm on v4.2.2 on Ubuntu 14.04.4 LTS. – Paul Ericson Sep 01 '16 at 00:42
  • @PaulEricson I am unable to repro this on any Ubuntu available to me. If your `$var` contains `~`, you will obviously need to pick a different delimiter. The "unterminated" error sounds like your `$var` contains a newline; you might be able to fix it by backslash-escaping every newline in the value, but I don't think this is entirely portable. This is by and large unrelated to the problem of slashes in the value. – tripleee Dec 12 '18 at 09:21
  • 1
    @PaulEricson Oh and your quoting is incorrect, you should not have the file name inside quotes. `sed -i "s~blah~$var~g" file` with quotes only around the actual `sed` script. – tripleee Jan 25 '19 at 12:08
7

This is an old question, but none of the answers here discuss operations other than s/from/to/ in much detail.

The general form of a sed statement is

*address* *action*

where address can be a regex range or a line number range (or empty, in which case the action is applied to every input line). So for example

sed '1,4d' file

will delete lines 1 through 4 (the address is the line number range 1,4 and the action is the d delete command); and

sed '/ick/,$s/foo/bar/' file

will replace the first occurrence of foo with bar on any line between the first match on the regex ick and the end of the file (the address is the range /ick/,$ and the action is the s substitute command s/foo/bar/).

In this context, if ick came from a variable, you could do

sed "/$variable/,\$s/foo/bar/"

(notice the use of double quotes instead of single, so that the shell can interpolate the variable, and the necessity to quote the literal dollar sign inside double quotes) but if the variable contains a slash, you will get a syntax error. (The shell expands the variable, then passes the resulting string to sed; so sed only sees literal text - it has no concept of the shell's variables.)

The cure is to use a different delimiter (where obviously you need to be able to use a character which cannot occur in the variable's value), but unlike in the s%foo%bar% case, you also need a backslash before the delimiter if you want to use a different delimiter than the default /:

sed "\\%$variable%,\$s/foo/bar/" file

(inside single quotes, a single backslash would obviously suffice); or you can separately escape every slash in the value. This particular syntax is Bash only:

sed "/${variable//\//\\/}/,\$s/foo/bar/" file

or if you use a different shell, try

escaped=$(echo "$variable" | sed 's%/%\\/%g')
sed "s/$escaped/,\$s/foo/bar/" file

For clarity, if $variable contained the string 1/2 then the above commands would be equivalent to

sed '\%1/2%,$s/foo/bar/' file

in the first case, and

sed '/1\/2/,$s/foo/bar/' file

in the second.

tripleee
  • 175,061
  • 34
  • 275
  • 318
5

Use Perl, where variables are first class citizens, not just expanding macros:

var=/Users/Documents/name/file perl -pe 's/\Q$ENV{var}/replace/g' $file
  • -p reads the input line by line and prints the line after processing
  • \Q quotes all the metacharacters in the following string (not needed for the value presented here, but necessary if the value contained [ or some other values special for regular expresisons)
choroba
  • 231,213
  • 25
  • 204
  • 289
  • 1
    The only generally useful answer to dozens of "using variables in sed expression" questions across Stack Exchange. Everything else is effectively "escape anything which is special to sed", which is practically useless given the variability of variables and of sed. – Heath Raftery May 16 '19 at 13:42
0

You can give sed command using any character as the delimiter. For example, with character # as sed's delimiter, you can replace cat with dog like so:

sed -i s#cat#dog#g test.txt

If you are using variables then

sed -i s#$var#replace#g $file
Piyush Singh
  • 2,736
  • 10
  • 26