3

I have a file like this:

1,2,subjects,\mat\hs,unix\,\Nato,\N,123,\N

I want to replace all occurences of \ with #, except for \N, such that the output looks like this:

1,2,subjects,#mat#hs,unix#,#Nato,\N,123,\N

I am trying to write a sed command which looks like-

sed -e 's@\\([^N])@#\1/g' filename

but this will fail for all values wherever there is a \N at the start for ex. \Nato in above example.

Could somebody please help me figure out a sed command(regex) for my requirement.

2

I'm not that well-versed in sed. But this can be done easily in perl using regex lookaround:

perl -pe 's/\\(?!N\b)/#/g' your_file_here

This will print the modified file to stdout. To make the substitutions in place, you can do:

perl -pi -e 's/\\(?!N\b)/#/g' your_file_here

Basically, this substitutes any backslash not followed by N\b (the character N at a word boundary) with #.

Edit

If you're absolutely sure that your entries are comma-delimited, you can do this to cover the case when \N is at the end of a word (as in SOLEM\N in the example you supplied):

perl -pe ' s/\\/#/g; s/(\A|,)\s*#N\s*(\Z|,)/$1\\N$2/g;' your_file_here

It simply substitutes any backslash with a hash mark then looks for #N found between two commas, between the beginning of the string and a comma or between a comma and the end of the string.

1
  • @user2118349 Answer updated to take the edge case you supplied into account. – Joseph R. Jul 29 '13 at 19:13
2
$ echo '1,2,subjects,\mat\hs,unix\,\Nato,\N,123,\N' \
  | sed -r -e 's@\\([^N]|N[^,])@#\1@g'
1,2,subjects,#mat#hs,unix#,#Nato,\N,123,\N

Edit for the \N as part of a word case: (see comments)

$ echo '1,2,subjects,\mat\hs,unix\,\SOLEM\N,\N,123,\N' \
  | sed -r -e 's@,\\N$@,:SINGLE_N_PLACEHOLDER:@g' \
           -e 's@^\\N,@:SINGLE_N_PLACEHOLDER:,@g' \
           -e 's@,\\N,@,:SINGLE_N_PLACEHOLDER:,@g' \
           -e 's@\\@#@g' \
           -e 's@:SINGLE_N_PLACEHOLDER:@\\N@g'
1,2,subjects,#mat#hs,unix#,#SOLEM#N,\N,123,\N

This is ugly and unreliable (placeholder string needs to be unique, will stop working if that pattern appears in the text), but I didn't find a working way to make sed use assertions the way PCRE/Perl support them.

You could make it shorter like so:

$ echo '1,2,subjects,\mat\hs,unix\,\SOLEM\N,\N,123,\N' \
  | sed -r -e 's@\\@#@g' \
           -e 's@(,|^)#N(,|$)@\1\\N\2@g'
1,2,subjects,#mat#hs,unix#,#SOLEM#N,\N,123,\N

But in this case, a single #N is the placeholder that must not already appear in the string.

8
  • Note that this will break if there's a space between \N and the , following it. – Joseph R. Jul 28 '13 at 20:13
  • I was thinking of using a negation of end-of-word \> after N but cannot seem to figure it out. Any ideas? – unxnut Jul 28 '13 at 20:18
  • @unxnut I don't think this is possible because \> is not really a character that can be negated: it matches a place in the regex (the place between a word character and a non-word one). I know perl offers a negated word boundary anchor (\B) but I'm not sure if sed's BREs support it. – Joseph R. Jul 28 '13 at 20:21
  • I am sure there should be a way to negate an entire pattern or search string; in this case, the search string is \\N\>. I am just hoping that some guru type person can provide a way of doing it or say why it cannot be done. – unxnut Jul 28 '13 at 20:25
  • @frostschutz Appreciate the quick help that offered, i am a new to regular expression. Wouldn't sed -r -e 's@\([^N]|N[^,])@#\1@g' fail whenvever \N would appear at the end of string for example- 1,2,subjects,\mat\hs,unix\,\SOLEM\N,\N,123,\N – user2118349 Jul 28 '13 at 20:54
2
sed -e 's@\\\([^N]\|\(N[a-zA-Z]\|$\)\)@#\1@g' your_file_here

Explaining: Replace all \ followed by

  • anything not N,
  • or N followed by any letter in [a-zA-Z] (extend this if needed)
  • end of line

with # and the rest of the matching pattern.

1
$ echo '1,2,subjects,\mat\hs,unix\,\Nato,\N,123,\N'   | 
         sed -r -e 's/\\N/XELI/g' | 
         sed -e 's/\\/#/g' | 
         sed -e 's/XELI/\\N/g'
1,2,subjects,#mat#hs,unix#,\Nato,\N,123,\N

I find it more readably to split it up:

  • replace \N by some non occuring character,
  • then replace / by #
  • and replace the /N back
1
  • 2
    Welcome to SX :). You haven't worked around the \Nato case mentioned in the OP. – Joseph R. Jul 28 '13 at 20:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.