1

So I want to take the questions found in sed + add word before string only if not exists and sed -- replace a character in a string when the preceding character is not a certain character and take it a little bit further.

Say I have a file and I want to replace all _ with \_, but I have two constraints:

  1. If _ is already preceded by a \ then I don't want anything to happen. (We never get \\ so no need to worry about that case)
  2. We should only make this replacement if _ appears before two delimiters. For example, between start[ and ]end.

As an example, the following:

Pretending_we have \_ some start[text that\_is really_cool]end.
Then \_nothing_ would start[happen_ to\_ that crew_]end

Would get converted to

Pretending_we have \_ some start[text that\_is really\_cool]end.
Then \_nothing_ would start[happen\_ to\_ that crew\_]end

Note: I know that in some cases we'd like to chain sed by replacing every instance of \_ with something that is not used and then replacing all _ with \_ and then reversing the first change. But I'd prefer to not do that because I don't know what other characters might be present in the code, so I'd like to do it directly if possible.

Also, I'll be doing this in both vim and terminal (vim as a test run to make sure it's working and then terminal to handle this on 11 different files.) I don't know enough about the differences between the two, but figured I'd mention it in case one is easier than another.

Edit: To answer some questions brought up:

  1. perl/sed/vim are all acceptable methods to handle the problem. Just not sure what the best approach was and I'm much more comfortable with sed/vim's regex and hence why I mentioned those. (By conflating sed and vim I think I caused confusion so sorry about that. I'm used to using sed and vim regex to handle most of my regex needs, and from what I've noticed usually what I do on one works perfectly on the other. So I'm assuming they're using the same regex handling, but that's probably not a safe assumption I should be making and will look that up. Sorry about the confusion).
  2. I'm using ubuntu
  3. Generally the start/end delimiters will be on the same line so theoretically that can be a safe assumption (although if you know how to do it so line doesn't matter, that would be good too for future people looking at a problem like this)
3
  • Wait, how is vim connected to sed? Are you asking for a sed command or a vim replacement command? Also, does this have to be sed or can we use something that understands more powerful regular expressions like perl? And most importantly: what operating system are you using? This is particularly relevant if you need sed since different systems have different sed implementations.
    – terdon
    Jan 8 at 19:27
  • 1
    And another thing, will these delimiters always be on the same line? Can you have start[ on line 4 and ]end on line 37?
    – terdon
    Jan 8 at 19:28
  • They'll generally be on the same line. Should have included that. The syntax for sed and vim is generally the same when using 's/.../.../g'. So I meant "the same" in that regards. It's just standard ubuntu (I'll update with these answers too) Jan 9 at 20:22

4 Answers 4

1

I'd use perl for that:

perl -pe 's{start\[.*?\]end}{$& =~ s{\\?_}{\\_}gr}ge' < your-file

Where we substitute¹ all (g) the start[...]end sequences with the same ($& which contains what was matched by the regexp) where we've substituded and returned all (g) the _s optionally (?) preceded by a \ with \_. The e flag is to tell that the replacement ($& =~ s{\\?_}{\\_}gr) is to be evaluated as code.

That assumes there are no nested start[...]end.

If start[...]ends may span several lines, add the -0777 option (set the record separator to something impossible), so the input be processed as a whole instead of one line at a time.


¹ using the same s/pattern/replacement/flags as in sed except that in perl we can also write it s{pattern}{replacement}flags which helps with nesting and with legibility

4
  • I guess I'm missing here where the delimiters are? Can you explain a little what's happening here? I'm not opposed to using perl, but not used to it. In your first example it looks like the s part would be the starting delimiter and gr would be the end? Or is this a substitution within a substitution? Jan 9 at 20:25
  • @AramPapazian, sorry I had overlooked that the delimiters were start[ and ]end, not just [ and ]. See edit. Jan 9 at 20:51
  • Awesome. Thank you =D So basically the $& is the text .*? in the first part of the substitution and you perform a secondary substitution on the string. Never seen this format, looks awesome =) Jan 15 at 8:46
  • @AramPapazian, $& contains what was matched by the whole regexp (start\[.*?\]end). If it contained only what was matched by .*?, that would end up removing the start[ and end[. Jan 15 at 9:25
1

GNU sed only:

sed -r ':1;s/(start\[.*[^\])(_.*\]end)/\1\\\2/;t1' file

If you need to make a replacement, the -i flag is set last -ri

0

You can use awk command for this job. It is convenient to temporarily replace square brackets by another character such as #

cat yourfile.txt| tr '[]' '#'

Now pipe the above result to this command:

awk -F# 'OFS=""; {for (i=1;i<=NF; i++)  if(i%2==0){ gsub("\\\\","",$i);gsub("_","\\_",$i);$i="["$i"]"} print $0}' 
3
  • 1
    I think this misses two key parts of what I asked: 1. I don't want a "temporary replacement" as it might throw in extra problems in there. (For example, there might be a # in the string which now will cause problems when I unreplace). 2. It looks like this is substituting all instances whereas I need it only between certain delimiters. Unless I'm misunderstanding? Jan 9 at 20:27
  • You can use any character, not just # (for example | is another option.). Also the code first removes all backslashes inside, and then puts a backslash ahead of all underlines. Just try it.
    – dhm
    Jan 9 at 22:36
  • That wouldn't allow for [, ], or \ in any other context in the input.
    – Ed Morton
    Jan 13 at 14:07
0

With GNU awk for multi-char RS, RT, and the 3rd arg to match():

$ awk -v RS=']end' '{ORS=RT} match($0,/(.*start\[)(.*)/,a) { gsub(/\\?_/,"\\_",a[2]); $0=a[1] a[2] } 1' file
Pretending_we have \_ some start[text that\_is really\_cool]end.
Then \_nothing_ would start[happen\_ to\_ that crew\_]end

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .