1

I have a text file needs to edit. The first field contains the string

"../../../"

and I want to replace with

"/home"

. I tried using sed, but .. and / are special characters and I am a little bit stumbled how to match these patterns. I tried using something like (..), but it seems not working for my case. Could anyone help me understand how to match this kind of pattern?

Shiva2794
  • 39
  • 8
Gu Buddy
  • 21
  • 3

4 Answers4

2

The . character is considered by sed as a wildcard, so it needs to be escaped. You will also need to escape the / character if you use it as a delimiter:

sed 's/\.\.\/\.\.\/\.\.\//\/home/g'
#     ^                  ^      ^  <-- locations of '/' delimiters
#          ^^    ^^    ^^ ^^       <-- locations of '\/' to match literal '/' characters

The first character you provide to sed is used as the delimiter. If your regex looks like /.../.../ or s/.../.../ then the delimiter becomes /. If you replace that / delimiter with something else, then you don't need to escape the literal '/' character you're searching for (or replacing with):

# Same as above, but with delimiters swapped from '/' to '@':
sed 's@\.\.\/\.\.\/\.\.\/@\/home@g'
#     ^                  ^      ^  <-- locations of '@' delimiters
#          ^^    ^^    ^^ ^^       <-- locations of '\/' to match literal '/' characters

Now with @ used as the delimiters instead of /, you can simplify/rewrite that with fewer escaped / characters:

sed 's@\.\./\.\./\.\./@/home@g'
#     ^               ^     ^  <-- locations of '@' delimiters
#          ^    ^    ^ ^       <-- locations of literal '/' characters

Note:

Sadly, \. isn't very readable, but necessary so you don't have this kind of edge case:

$ echo 'ab/../cd/' | sed 's@../../../@/home@g'
/home

The above SHOULD be left unmodified:

$ echo 'ab/../cd/' | sed 's@\.\./\.\./\.\./@/home@g'
ab/../cd/
OnlineCop
  • 4,019
  • 23
  • 35
  • 1
    If you want to avoid backslashes entirely, you can stick the period in a single character grouping `[.]` So, this: `sed 's#[.][.]/[.][.]/[.][.]#/home#'` Still ugly. – stevesliva Jan 03 '19 at 22:15
  • A non-sed method could be to use `\Q` and `\E`: `perl -pe 's@(\Q../\E){3}@/home@g'` or `perl -pe 's@\Q../../../\E@/home@g'` so backslash and dot don't need to be escaped. – OnlineCop Jan 07 '19 at 18:42
1

Slash / and dot . are both special regex characters to sed, so they need to be escaped if they are being used for their literal characters.
Dot is the single-character wildcard, so .. matches any two characters, not just a literal two-dots.
Slash delimits the search, replace, and flags, as the man page for sed states /regular expression/replacement/flags -- so your search pattern has to escape both of those, making it

sed 's/\.\.\/\.\.\/\.\./home/g' file > newfile

That gets pretty ugly, but sed can use something other than / as the delimiter. My favorite alternate is ~, so the command would become

sed 's~\.\./\.\./\.\.~home~g' file > newfile

Update
Responding to @Gu Buddy's comment...

I don't know that it's "more elegant", but there are other ways to approach this.

The special characters such as . * / lose their special meaning when used in a character class, so [.] just means period not "any char", so you can avoid escaping them

sed 's/[.][.][/]/dot-dot-slash/g' file
sed 's/[.][.][/][.][.][/][.][.]/home/g' file

You can also use a match count (repetition) — a number or range in curly-braces, applied to the char or group preceding it — but those have to be escaped unless you use extended regular expressions ("ERE" vs basic regex "BRE"), enabled via the -E flag:

    sed 's~\([.][.][/]\)\{3\}~home/~' file  # with BRE
group start-^  grp end-^  ^-count
    sed -E 's~([.][.][/]){3}~home/~' file   # with ERE
    sed -E 's~([.]{2}[/]){3}~home/~' file   # also ERE

Notice in my original answer I avoided replacing the third slash, leaving it there to separate the replacement "home" from the remaining path...

../../../
        ^

...but using the repetition of {3} it will match and replace that third slash, so I have to include the slash after home in the replacement string.

I tested all of these on a file that just contains this:

../../../this/that/file.txt
../../../some/otherfile.txt

getting this output:

home/this/that/file.txt
home/some/otherfile.txt
Stephen P
  • 14,422
  • 2
  • 43
  • 67
  • Thank you for your comments! I did figure it out eventually, but I was wondering if there is a more elegant way to write this rather than using escape on each special character, haha. – Gu Buddy Jan 04 '19 at 15:07
0

You need to escape the slashes and dots:

sed 's/\.\.\/\.\.\/\.\./\/home/g' file.txt > newfile.txt
Tim
  • 2,756
  • 1
  • 15
  • 31
0

We can use regex to get the desired output.

echo "../../../" | sed -r 's/(..\/){3}/\/home/g'

Option Detail

According to man page of sed

-E, -r, --regexp-extended

use extended regular expressions in the script (for portability use POSIX -E).

REGEX Details

(../){3} : It will provide "../../../"

As we are using (/) in sed ex. sed 's///g' so we can not use normal (/) in sed so we need to escape this character by (/)

As this is to specific to (../) 3 times. If you want to change all (../) add * in place of {3}. ex. (../)*

/home : It gives "/home"

If you want one more (/) after home you can add (/home/)

sed -r 's/ (../){3} / /home /g'

So we are just substituting "/home" in place of "../../../". Now, you can change it as per your need.

Mohit Rathore
  • 428
  • 3
  • 10