[SOLVED] Replace 2nd occurrence of a string in a file - sed or awk?
ProgrammingThis forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.
Notices
Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
If you have any problems with the registration process or your account login, please contact us. If you need to reset your password, click here.
Having a problem logging in? Please visit this page to clear all LQ-related cookies.
Get a virtual cloud desktop with the Linux distro that you want in less than five minutes with Shells! With over 10 pre-installed distros to choose from, the worry-free installation life is here! Whether you are a digital nomad or just looking for flexibility, Shells can put your Linux machine on the device that you want to use.
Exclusive for LQ members, get up to 45% off per month. Click here for more info.
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.
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
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.
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
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.
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.
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
}
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.
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.