LinuxQuestions.org
Download your favorite Linux distribution at LQ ISO.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 04-05-2010, 01:56 PM   #1
kushalkoolwal
Senior Member
 
Registered: Feb 2004
Location: Middle of nowhere
Distribution: Debian Squeeze
Posts: 1,249

Rep: Reputation: 49
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

Last edited by kushalkoolwal; 04-05-2010 at 03:23 PM.
 
Old 04-05-2010, 02:42 PM   #2
David the H.
Bash Guru
 
Registered: Jun 2004
Location: Osaka, Japan
Distribution: Arch + Xfce
Posts: 6,852

Rep: Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037
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.

Last edited by David the H.; 04-05-2010 at 04:35 PM. Reason: fixed mistake
 
1 members found this post helpful.
Old 04-05-2010, 06:52 PM   #3
ghostdog74
Senior Member
 
Registered: Aug 2006
Posts: 2,697
Blog Entries: 5

Rep: Reputation: 244Reputation: 244Reputation: 244
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

Last edited by ghostdog74; 04-05-2010 at 06:55 PM.
 
1 members found this post helpful.
Old 04-06-2010, 08:00 PM   #4
Star_Gazer
Member
 
Registered: Aug 2009
Location: Virginia, United States
Distribution: openSUSE 11.2 KDE
Posts: 34

Rep: Reputation: 16
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.
 
1 members found this post helpful.
Old 04-07-2010, 06:36 AM   #5
David the H.
Bash Guru
 
Registered: Jun 2004
Location: Osaka, Japan
Distribution: Arch + Xfce
Posts: 6,852

Rep: Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037Reputation: 2037
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.

Last edited by David the H.; 04-07-2010 at 06:48 AM. Reason: Added some clarification
 
3 members found this post helpful.
Old 04-07-2010, 06:57 AM   #6
syg00
LQ Veteran
 
Registered: Aug 2003
Location: Australia
Distribution: Lots ...
Posts: 21,120

Rep: Reputation: 4120Reputation: 4120Reputation: 4120Reputation: 4120Reputation: 4120Reputation: 4120Reputation: 4120Reputation: 4120Reputation: 4120Reputation: 4120Reputation: 4120
ghostdog74, unless I'm misunderstanding (quite likely), won't that change every second occurrence, not just the second one ?.
 
Old 04-07-2010, 07:43 AM   #7
Star_Gazer
Member
 
Registered: Aug 2009
Location: Virginia, United States
Distribution: openSUSE 11.2 KDE
Posts: 34

Rep: Reputation: 16
Quote:
Originally Posted by David the H. View Post
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.
 
Old 04-07-2010, 09:45 AM   #8
grail
LQ Guru
 
Registered: Sep 2009
Location: Perth
Distribution: Manjaro
Posts: 10,005

Rep: Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191Reputation: 3191
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
 
Old 04-07-2010, 08:17 PM   #9
ghostdog74
Senior Member
 
Registered: Aug 2006
Posts: 2,697
Blog Entries: 5

Rep: Reputation: 244Reputation: 244Reputation: 244
Quote:
Originally Posted by syg00 View Post
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
 
Old 05-02-2011, 01:38 PM   #10
typer100
LQ Newbie
 
Registered: May 2011
Posts: 1

Rep: Reputation: 0
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

Last edited by typer100; 05-02-2011 at 01:42 PM.
 
Old 05-02-2011, 02:30 PM   #11
colucix
LQ Guru
 
Registered: Sep 2003
Location: Bologna
Distribution: CentOS 6.5 OpenSuSE 12.3
Posts: 10,509

Rep: Reputation: 1983Reputation: 1983Reputation: 1983Reputation: 1983Reputation: 1983Reputation: 1983Reputation: 1983Reputation: 1983Reputation: 1983Reputation: 1983Reputation: 1983
Code:
echo "$testline" | sed -r 's/(.*foo.*)foo(.*foo.*)foo/\1FOO\2FOO/'
foo bar FOO bar foo bar FOO bar
 
1 members found this post helpful.
Old 06-10-2020, 06:29 AM   #12
kropex
LQ Newbie
 
Registered: Apr 2020
Posts: 14

Rep: Reputation: 7
Angry

Why this thread is SOLVED? I really don't see the solution even if the starting subject is very well explained. Just Nth element replace in a file not line using sed is a challenge over entire internet searching. This thread is very good subject, it is solved but can't get the solution.
 
Old 06-10-2020, 06:36 AM   #13
shruggy
Senior Member
 
Registered: Mar 2020
Posts: 3,670

Rep: Reputation: Disabled
Sorry? The awk solution is in #3, by ghostdog74. Amended by grail in #8. Admittedly, the solution is not perfect as it doesn't provide for the case when the second occurrence happens on the same line as the first.

Recent GNU sed versions (4.2.2+) support NULL terminated lines, so for text files of reasonable size this should work better:
Code:
sed -z 's/Jack/Rob/2' file
If we're speaking about replacing words (however they have been defined) as opposite to arbitrary strings then awk would cut it better again:
Code:
BEGIN{
  FS = "[^[:alnum:]]+"
}
{
  for (i=1; i<=NF; i++){
    word[$i]++
    if (word[$i]==2 && $i=="Jack") $i="Rob"
  }
  print
}

Last edited by shruggy; 06-10-2020 at 12:18 PM.
 
Old 06-10-2020, 03:11 PM   #14
kropex
LQ Newbie
 
Registered: Apr 2020
Posts: 14

Rep: Reputation: 7
Ok, let's give the solution expected by anyone, also expected by me, I worked a lot to solve it in this way.
The idea is to obtain the line number with the searched word in file and to replace directly at the line found.

Code:
    #first line here generate a unique MAC for my file
    uniqueMAC=$(echo 42:21:10$(hexdump -n3 -e '/1 ":%02x"' /dev/random))

    #next line my variable get the format which should be replaces
    MACxml="address='$uniqueMAC'\/>" #the \ here is escape for this line when it is used in sed

    #next line find the lines which contain the string and get the line number, there could be more than one number
    mod_lines=$(grep -n 'address=' "$myfile.xml" |cut -d':' -f1)

    #next line is pushing the line numbers from string into array to address individually as I need
    no_lines=($(echo $mod_lines | tr " " "\n"))

    #finally here is the sed which replace the text exactly at the line number I want
    sed -i ''${no_lines[1]}' s/address=.*/'$MACxml'/' $myfile.xml; 
#here above index [1] is second element line number, and the replace will occur for the text 'address=' and all text after in that line
The code above was tested and works great, I hope will help a lot of people and we can consider this thread really SOLVED.
Thanks to anyone especially to thread initiator.

Last edited by kropex; 06-10-2020 at 03:21 PM.
 
Old 06-10-2020, 05:53 PM   #15
shruggy
Senior Member
 
Registered: Mar 2020
Posts: 3,670

Rep: Reputation: Disabled
Glad you sorted it out. But sed is a wrong tool to parse XML. In your particular case, this really feels like another example of the XY problem.

Suppose your input data look like this (example taken from the libvirt docs):
Code:
<domain type='qemu'>
  <devices>
    <interface type='server'>
      <mac address='52:54:00:22:c9:42'/>
      <source address='192.168.0.1' port='5558'/>
    </interface>
    <interface type='client'>
      <mac address='52:54:00:8b:c9:51'/>
      <source address='192.168.0.1' port='5558'/>
    </interface>
  </devices>
</domain>
Then, to change the MAC address on the second interface, do
Code:
xmlstarlet ed -u "//interface[@type='client']/mac/@address" -v 52:54:00:8b:c9:43 myfile.xml

Last edited by shruggy; 06-10-2020 at 07:32 PM.
 
1 members found this post helpful.
  


Reply

Tags
awk, sed



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
Problem using sed to replace string in file umk Debian 12 02-01-2012 08:39 AM
Sed/awk/grep search for number string of variable length in text file Alexr Linux - Newbie 10 01-19-2010 01:34 PM
Using sed/awk to replace a string at a given position in anoopvraj Linux - Newbie 6 05-30-2009 07:59 AM
how to find and replace only the 2nd occurrence of similar string in a file hchoonbeng Linux - Newbie 1 10-08-2008 03:44 AM
SED replace string by occurrence uttam_h Programming 5 03-05-2008 10:02 PM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 04:50 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration