176

I have a Visual Studio project, which is developed locally. Code files have to be deployed to a remote server. The only problem are the URLs they contain, which are hard-coded.

The project contains URLS such as ?page=one. For the link to be valid on the server, it must be /page/one .

I've decided to replace all URLS in my code files with sed before deployment, but I'm stuck on slashes.

I know this is a not a pretty solution, but it's simple and would save me a lot of time. The total number of strings I have to replace is fewer than 10. Total number of files which have to be checked is ~30.

Example describing my situation is below:

Command I'm using:

sed -f replace.txt < a.txt > b.txt

replace.txt which contains all the strings:

s/?page=one&/pageone/g
s/?page=two&/pagetwo/g
s/?page=three&/pagethree/g

a.txt:

?page=one&
?page=two&
?page=three&

Content of b.txt after I run my sed command:

pageone
pagetwo
pagethree

What I want b.txt to contain:

/page/one
/page/two
/page/three
Benjamin W.
  • 38,596
  • 16
  • 96
  • 104
afaf12
  • 4,938
  • 7
  • 35
  • 56

11 Answers11

319

The easiest way would be to use a different delimiter in your search/replace lines, e.g.:

s:?page=one&:pageone:g

You can use any character as a delimiter that's not part of either string. Or, you could escape it with a backslash:

s/\//foo/

Which would replace / with foo. You'd want to use the escaped backslash in cases where you don't know what characters might occur in the replacement strings (if they are shell variables, for example).

lurker
  • 55,215
  • 8
  • 64
  • 98
  • 1
    > Or, you could escape it with a backslash. An example of that would be more useful, since you don't always know what characters are in a string to be able to choose something different. eg, this: echo / | sed s/\//a/g does not work: sed: -e expression #1, char 5: unknown option to `s' – Max Waterman Sep 03 '19 at 03:42
  • 1
    Could you add one then? Thanks :) I found surrounding in double quotes seems to work: echo / | sed "s/\//a/g" – Max Waterman Sep 04 '19 at 20:47
  • @MaxWaterman it's standard operating procedure when using `sed` that the regex command is put in double quotes. I didn't use them in my answer because I wasn't showing the whole `sed` command line but just the `sed` regex command string as the OP had done. If you put it in a file, as the OP did, you don't need the quotes. – lurker Sep 04 '19 at 23:58
  • Yeah, fair enough (though perhaps it could be mentioned). That example helps. I have been finding I need to put in lots and lots of backslashes sometimes...and it gets really confusing. eg -e "s/'/\\\\\\\&/g" I think the text is wrong, though: "Which would replace \ with foo" - should be "Which would replace / with foo", no? – Max Waterman Sep 06 '19 at 01:27
  • @MaxWaterman thanks for catching that on \ vs. /. Fixed it. If you have a `sed` command in a shell script, then more backslashes may be necessary (each backslash needs to be backslashed again). – lurker Sep 06 '19 at 11:10
  • Not that this applies only to subsitute (`s`) you can use only `/` when inserting a line before or after a match. You're free to use as much `/`'s after the second `/` (in the addition leg): `sed -i '/^.*my.match.pattern.*$/i \ ' logback.xml – dr jerry Sep 03 '21 at 13:26
  • @Tom Anderson's solution is far more elegant, which allows your source code to be readable (with slashes). – Abel Wenning Dec 24 '21 at 00:52
  • @AbelWenning Tom Anderson's solution is basically the same as mine. He just chose `#` instead of `:` as his delimeter as preference and indicated that he preferred `#`. My point was that you can pick anything you want that's not in the search or replace string. I was not trying to cite `:` as a particular favored choice. – lurker Dec 24 '21 at 01:52
  • @lurker I should have specified that the problem with a colon (`:`) is that it is common in things like URLs and JSON, whereas octothorpe (`#`) is uncommon in syntaxes. – Abel Wenning Jan 17 '22 at 22:46
  • 1
    @AbelWenning yes, it is, I would agree. The point of my answer, though, wasn't to call out the colon as the best universal alternative. It was just showing that an alternative was allowed by the syntax. The colon just happened to be the example I whimsically chose. I suppose if the OP's examples had "http://" prefix my whim probably would have taken me another direction. :) – lurker Jan 18 '22 at 01:31
121

The s command can use any character as a delimiter; whatever character comes after the s is used. I was brought up to use a #. Like so:

s#?page=one&#/page/one#g
Tom Anderson
  • 44,913
  • 16
  • 86
  • 129
  • 5
    The man page for the BSD sed on OS X says of the *s* command: *Substitute the replacement string for the first instance of the regular expression in the pattern space. Any character other than backslash or newline can be used instead of a slash to delimit the RE and the replacement.* I would bet money that the man page for GNU sed says something similar. – Tom Anderson Aug 09 '14 at 20:37
  • 1
    The current accepted answer is basically the same as this one, and was posted a minute earlier! – Tom Anderson Sep 17 '18 at 09:27
74

A very useful but lesser-known fact about sed is that the familiar s/foo/bar/ command can use any punctuation, not only slashes. A common alternative is s@foo@bar@, from which it becomes obvious how to solve your problem.

John Zwinck
  • 223,042
  • 33
  • 293
  • 407
12

add \ before special characters:

s/\?page=one&/page\/one\//g

etc.

pkamb
  • 30,595
  • 22
  • 143
  • 179
Ilja
  • 1,155
  • 1
  • 15
  • 33
  • 4
    I may have missed something, but I've tried this and it doesn't seem to work. It did seem the obvious thing to try, but assuming I'm right and it indeed doesn't work, why post it? – codenoob Jan 07 '16 at 00:22
  • 4
    @codenoob (and anyone else who gets here) -- the 's' at the beginning is required. `s/foo\/bar/foo_bar/` will work, but `/foo\/bar/foo_bar/` won't. – MynockSpit May 05 '18 at 22:33
5

In a system I am developing, the string to be replaced by sed is input text from a user which is stored in a variable and passed to sed.

As noted earlier on this post, if the string contained within the sed command block contains the actual delimiter used by sed - then sed terminates on syntax error. Consider the following example:

This works:

$ VALUE=12345
$ echo "MyVar=%DEF_VALUE%" | sed -e s/%DEF_VALUE%/${VALUE}/g
MyVar=12345

This breaks:

$ VALUE=12345/6
$ echo "MyVar=%DEF_VALUE%" | sed -e s/%DEF_VALUE%/${VALUE}/g
sed: -e expression #1, char 21: unknown option to `s'

Replacing the default delimiter is not a robust solution in my case as I did not want to limit the user from entering specific characters used by sed as the delimiter (e.g. "/").

However, escaping any occurrences of the delimiter in the input string would solve the problem. Consider the below solution of systematically escaping the delimiter character in the input string before having it parsed by sed. Such escaping can be implemented as a replacement using sed itself, this replacement is safe even if the input string contains the delimiter - this is since the input string is not part of the sed command block:

$ VALUE=$(echo ${VALUE} | sed -e "s#/#\\\/#g")
$ echo "MyVar=%DEF_VALUE%" | sed -e s/%DEF_VALUE%/${VALUE}/g
MyVar=12345/6

I have converted this to a function to be used by various scripts:

escapeForwardSlashes() {

     # Validate parameters
     if [ -z "$1" ]
     then
             echo -e "Error - no parameter specified!"
             return 1
     fi

     # Perform replacement
     echo ${1} | sed -e "s#/#\\\/#g"
     return 0
}
Yoav Drori
  • 51
  • 1
  • 1
  • 1
    The take away from your answer for me, was that if the VALUE you're using to replace DEF_VALUE, has forward slashes in it, then you have to escape them with 3 backslashes for sed to work e.g. `VALUE="01\\\/01\\\/2018"` – alexkb Oct 31 '18 at 06:10
3

this line should work for your 3 examples:

sed -r 's#\?(page)=([^&]*)&#/\1/\2#g' a.txt
  • I used -r to save some escaping .
  • the line should be generic for your one, two three case. you don't have to do the sub 3 times

test with your example (a.txt):

kent$  echo "?page=one&
?page=two&
?page=three&"|sed -r 's#\?(page)=([^&]*)&#/\1/\2#g'
/page/one
/page/two
/page/three
Kent
  • 181,427
  • 30
  • 222
  • 283
2

please see this article http://netjunky.net/sed-replace-path-with-slash-separators/

Just using | instead of /

capcom923
  • 480
  • 4
  • 14
1

replace.txt should be

s/?page=/\/page\//g
s/&//g
Ion Cojocaru
  • 2,573
  • 14
  • 16
1

Great answer from Anonymous. \ solved my problem when I tried to escape quotes in HTML strings.

So if you use sed to return some HTML templates (on a server), use double backslash instead of single:

var htmlTemplate = "<div style=\\"color:green;\\"></div>";
Sirar Salih
  • 2,467
  • 2
  • 18
  • 18
0

sed is the stream editor, in that you can use | (pipe) to send standard streams (STDIN and STDOUT specifically) through sed and alter them programmatically on the fly, making it a handy tool in the Unix philosophy tradition; but can edit files directly, too, using the -i parameter mentioned below.
Consider the following:

sed -i -e 's/few/asd/g' hello.txt

s/ is used to substitute the found expression few with asd:

The few, the brave.


The asd, the brave.

/g stands for "global", meaning to do this for the whole line. If you leave off the /g (with s/few/asd/, there always needs to be three slashes no matter what) and few appears twice on the same line, only the first few is changed to asd:

The few men, the few women, the brave.


The asd men, the few women, the brave.

This is useful in some circumstances, like altering special characters at the beginnings of lines (for instance, replacing the greater-than symbols some people use to quote previous material in email threads with a horizontal tab while leaving a quoted algebraic inequality later in the line untouched), but in your example where you specify that anywhere few occurs it should be replaced, make sure you have that /g.

The following two options (flags) are combined into one, -ie:

-i option is used to edit in place on the file hello.txt.

-e option indicates the expression/command to run, in this case s/.

Note: It's important that you use -i -e to search/replace. If you do -ie, you create a backup of every file with the letter 'e' appended.

Chaminda Bandara
  • 1,847
  • 2
  • 26
  • 30
0

A simplier alternative is using AWK as on this answer:

awk '$0="prefix"$0' file > new_file

20yco
  • 866
  • 6
  • 27
Mark
  • 1