LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (http://www.linuxquestions.org/questions/programming-9/)
-   -   Replace 2nd occurrence of a string in a file - sed or awk? (http://www.linuxquestions.org/questions/programming-9/replace-2nd-occurrence-of-a-string-in-a-file-sed-or-awk-800171/)

kushalkoolwal 04-05-2010 01:56 PM

Replace 2nd occurrence of a string in a file - sed or awk?
 
So I know how to replace a particular instance (say 3rd one) of a word in a line using sed based on the sed one-liners. However I would like to replace a particular instance of a word in the entire file. I tried searching on the Internet but did not find anything useful.

For example, here is a file:
Code:

John
Betty
Jack
Ron
Jack
Paul

So now I would like to replace the second instance of Jack (in red color) with "Rob" (for example). Not quite sure how to do that? I tried couple of things from here but they did not work.

Thanks

David the H. 04-05-2010 02:42 PM

Try this.

Code:

sed  '0,/Jack/! s/Jack/Rob/' file.txt
The exclamation mark negates everything from the beginning of the file to the first "Jack", so that the substitution operates on all the following lines. Note that I believe this is a gnu sed operation only.

If you need to operate on only the second occurrence, and ignore any subsequent matches, you can use a nested expression.
Code:

sed  '0,/Jack/! {0,/Jack/ s/Jack/Rob/}' file.txt
Here, the bracketed expression will operate on the output of the first part, but in this case, it will exit after changing the first matching "Jack".

PS, I've found the sed faq to be very helpful in cases like this.

ghostdog74 04-05-2010 06:52 PM

use awk, where you can maintain a count of things. Its also easier to change if your future requirement is not the second instance.

Code:

awk '/Jack/{c++;if(c==2){sub("Jack","Rob");c=0}}1' file

Star_Gazer 04-06-2010 08:00 PM

Interesting...

In the page of the document,
info:/sed/The "s" Command
which can be loaded in Konquerer.

Code:

The `s' command can be followed by zero or more of the following FLAGS:
 `g'
      Apply the replacement to _all_ matches to the REGEXP, not just the
      first.


 `NUMBER'
      Only replace the NUMBERth match of the REGEXP.


      Note: the POSIX standard does not specify what should happen when
      you mix the `g' and NUMBER modifiers, and currently there is no
      widely agreed upon meaning across `sed' implementations.  For GNU
      `sed', the interaction is defined to be: ignore matches before the
      NUMBERth, and then match and replace all matches from the NUMBERth
      on.

Though I haven't quite got a grasp on it. :o

David the H. 04-07-2010 06:36 AM

When you add a number after the substitution expression, the replacement will happen to the nth occurrence of the pattern on that line; just as 'g' will affect all occurrences on the same line. Combining the two makes it affect all matches from that position on.

Code:

testline="foo bar foo bar foo bar foo bar"

$ echo "$testline" | sed 's/foo/FOO/'
FOO bar foo bar foo bar foo bar

$ echo "$testline" | sed 's/foo/FOO/3'
foo bar foo bar FOO bar foo bar

$ echo "$testline" | sed 's/foo/FOO/g'
FOO bar FOO bar FOO bar FOO bar

$ echo "$testline" | sed 's/foo/FOO/3g'
foo bar foo bar FOO bar FOO bar

Sed works by copying a single line into it's pattern buffer, processing that line, then clearing it and moving on to the next line. This means that the s/// expression on its own cannot affect multiple lines. That's what the addressing expressions and the hold buffer are for.

Good catch on a poorly-known function though. :)

syg00 04-07-2010 06:57 AM

ghostdog74, unless I'm misunderstanding (quite likely), won't that change every second occurrence, not just the second one ?.

Star_Gazer 04-07-2010 07:43 AM

Quote:

Originally Posted by David the H. (Post 3927300)
When you add a number after the substitution expression, the replacement will happen to the nth occurrence of the pattern on that line; just as 'g' will affect all occurrences on the same line. Combining the two makes it affect all matches from that position on.

Code:

testline="foo bar foo bar foo bar foo bar"

$ echo "$testline" | sed 's/foo/FOO/'
FOO bar foo bar foo bar foo bar

$ echo "$testline" | sed 's/foo/FOO/3'
foo bar foo bar FOO bar foo bar

$ echo "$testline" | sed 's/foo/FOO/g'
FOO bar FOO bar FOO bar FOO bar

$ echo "$testline" | sed 's/foo/FOO/3g'
foo bar foo bar FOO bar FOO bar

Sed works by copying a single line into it's pattern buffer, processing that line, then clearing it and moving on to the next line. This means that the s/// expression on its own cannot affect multiple lines. That's what the addressing expressions and the hold buffer are for.

Good catch on a poorly-known function though. :)

Ah, thanks for pointing that out :)

It was driving me batty when I kept trying to test it, thus, I gave up.

I'll note this for future references. :hattip:

grail 04-07-2010 09:45 AM

Quote:

ghostdog74, unless I'm misunderstanding (quite likely), won't that change every second occurrence, not just the second one ?.
Yes but if you take out the c=0 at the end, problem solved ;)

ghostdog74 04-07-2010 08:17 PM

Quote:

Originally Posted by syg00 (Post 3927326)
ghostdog74, unless I'm misunderstanding (quite likely), won't that change every second occurrence, not just the second one ?.

yes, i interpreted OP's req'd that way. If its only the 2nd instance, then as grail mentioned, remove the c=0

typer100 05-02-2011 01:38 PM

Old thread, but I have another question. I'll use the current example...

testline="foo bar foo bar foo bar foo bar"

Let say I want to replace occurrence 2 and 4 with FOO. I can obviously this with 2 commands...

echo "$testline" | sed 's/foo/FOO/2'|sed 's/foo/FOO/3'
foo bar FOO bar foo bar FOO bar

It works but, any chase for a one liner... s/foo/FOO/2,4

colucix 05-02-2011 02:30 PM

Code:

echo "$testline" | sed -r 's/(.*foo.*)foo(.*foo.*)foo/\1FOO\2FOO/'
foo bar FOO bar foo bar FOO bar



All times are GMT -5. The time now is 09:57 PM.