Insert some text after Nth matching pattern using sed - sed

How can I do so? I have multiple e.g. foobar patterns in my file, how can I add after e.g. the 4th one some_text?

Does the following work for you?
sed ':a;$!{N;ba};s/\(foobar\)/\1\nsome_text/4' inputfile
For the input:
$ cat inputfile
line
foobar
foobar
foobar
line
foobar
line
foobar
line
foobar
line
This would generate
$ sed ':a;$!{N;ba};s/\(foobar\)/\1\nsome_text/4' inputfile
line
foobar
foobar
foobar
line
foobar
some_text
line
foobar
line
foobar
line
Use -e on FreeBSD:
sed -e :a -e '$!N' -e '$!ba' -e 's/\(foobar\)/\1\nsome_text/4' inputfile

an awk version:
awk '/foo/{x++} x==4{sub(/foo/,"&some_text")}1' file
example:
kent$ cat file
foo
foo
trash
foo
trash
foo
foo
foo
kent$ awk '/foo/{x++} x==4{sub(/foo/,"&some_text")}1' file
foo
foo
trash
foo
trash
foosome_text
foo
foo

Related

Replace complete line getting number from variable

I have a file with a certain line, let's say...
AAA BBB CCC
I need to replace that entire line, after finding it, so I did:
q1=`grep -Hnm 1 "AAA" FILE | cut -d : -f 2`
That outputs me the line number of the first occurrence (in q1), because it has more than one occurrence, now, here comes my problem... In a previous step I was using this sed to replace a certain line in the file:
sed -e '3s/.*/WHATEVER/' FILE
To replace (in the example, line 3) the full line with WHATEVER, but now if I try to use $q1 instead of the "3" indicating the line number it doesn't work:
sed -e '$q1s/.*/WHATEVER/' FILE
It's probably a stupid syntax mistake, any help is welcome; thanks in advance
Try:
sed -e "${q1}s/.*/WHATEVER/" FILE
I'd use awk for this:
awk '/AAA/ && !r {print "WHATEVER"; r=1; next} {print}' <<END
a
b
AAA BBB CCC
d
e
AAA foo bar
f
END
a
b
WHATEVER
d
e
AAA foo bar
f
If you want to replace the first occurrence of a string in a file, you could use this awk script:
awk '/occurrence/ && !f++ {print "replacement"; next}1' file
The replacement will only be printed the first time, as !f++ will only evaluate to true once (on subsequent evaluations, f will be greater than zero so !f will be false. The 1 at the end is always true, so for each line other than the matched one, awk does the default action and prints the line.
Testing it out:
$ cat file
blah
blah
occurrence 1 and some other stuff
blah
blah
some more stuff and occurrence 2
blah
$ awk '/occurrence/ && !f++ {print "replacement"; next}1' file
blah
blah
replacement
blah
blah
some more stuff and occurrence 2
blah
The "replacement" string could easily be set to the value of a shell variable in the following way:
awk -v rep="$r" '/occurrence/ && !f++ {print rep; next}1' file
where $r is a shell variable.
Using the same file as above and the example variable in your comment:
$ q2="x=\"Second\""
$ awk -v rep="$q2" '/occurrence/ && !f++ {print rep; next}1' file
blah
blah
x="Second"
stuff
blah
blah
some more stuff and occurrence 2
blah
sed "${q1} c\
WHATEVER" YourFile
but you can directly use
sed '/YourPatternToFound/ {s/.*/WHATEVER/
:a
N;$!ba
}' YourFile

sed script to remove everything in brackets over multiple lines

I am trying to use sed script to remove the content of an array in a file. I have tried to delete the content to only leave the brackets (). However I can't get the sed script to work over multiple lines.
I am trying to change the current state of the file:
LIST = ( "content"
"content1"
"content3")
to this:
LIST = ()
However the sed script I am using only changes the file to this:
LIST = ()
"content"
"content1"
"content2"
sed -e 's/LIST=\([^)]*\)/LIST=() /g' filename
I should also mention there are other sets of brackets in the file which I don't want affected.
e.g
LISTNUMBER2("CONTENT")
should not be emptied.
this sed one-liner works for your example:
sed -n '1!H;1h;${x;s/(.*)/()/;p}'
test:
kent$ echo 'LIST = ( "content"
"content1"
"content3")'|sed -n '1!H;1h;${x;s/(.*)/()/;p}'
LIST = ()
if you could use awk, this one-liner works for your example too:
awk -v RS="" '{sub(/\(.*\)/,"()")}1'
test:
kent$ echo 'LIST = ( "content"
"content1"
"content3")'|awk -v RS="" '{sub(/\(.*\)/,"()")}1'
LIST = ()
EDIT for OP's comment
multi brackets situation:
awk
awk -v RS="\0" -v ORS="" '{gsub(/LIST\s*=\s*\([^)]*\)/,"LIST = ()")}1' file
test:
kent$ cat file
LISTKEEP2("CONTENT")
LIST = ( "content"
"content1"
"content3")
LISTNUMBER2("CONTENT")
kent$ awk -v RS="\0" -v ORS="" '{gsub(/LIST\s*=\s*\([^)]*\)/,"LIST = ()")}1' file
LISTKEEP2("CONTENT")
LIST = ()
LISTNUMBER2("CONTENT")
sed:
sed -nr '1!H;1h;${x;s/(LIST\s*=\s*\()[^)]*\)/\1)/;p}' file
kent$ sed -nr '1!H;1h;${x;s/(LIST\s*=\s*\()[^)]*\)/\1)/;p}' file
LISTKEEP2("CONTENT")
LIST = ()
LISTNUMBER2("CONTENT")
Another sed solution:
sed '/LIST = (/{:next;/)/{s/(.*)/()/;b;};N;b next;}'
Here's a version that would not change any block containing a certain string ("keepme" in this example, but could be anything):
sed '/LIST = (/{:next;/)/{/keepme/b;s/(.*)/()/;b;};N;b next;}'
Since this does the keepme test after it finds the closing parenthesis that tag can be anywhere in the block.

Is it possible to tell sed to perform a maximum of one substitution per line?

Is it possible to encapsulate the following pseudocode using sed?
for line in lines:
if line == "foo":
print "FOO"
else:
print "- " + line
Here's the first thing I tried:
> echo 'foo
> bar
> baz' | sed -e 's/^foo$/FOO/' -e 's/^/- /'
- FOO
- bar
- baz
This is incorrect since both substitutions are applied to the first line.
Is it possible to tell sed to perform a maximum of one substitution per line?
You can limit what lines a substitution affects, by prefixing it with a pattern:
sed -e '/^foo$/! s/^/- /' -e '/^foo$/ s//FOO/' infile
A better alternative is to use the t branch command which will go to the next line if the previous substitution succeeded:
sed 's/^foo$/FOO/; t; s/^/- /' infile
Or the more portable:
sed -e 's/^foo$/FOO/' -e t -e 's/^/- /' infile
Output in both cases:
FOO
- bar
- baz

sed replace if part of word matches

My text looks like this:
cat
catch
cat_mouse
catty
I want to replace "cat" with "dog".
When I do
sed "s/cat/dog/"
my result is:
dog
catch
cat_mouse
catty
How do I replace with sed if only part of the word matches?
There's a mistake :
You lack the g modifier
sed 's/cat/dog/g'
g
Apply the replacement to all matches to the regexp, not just the first.
See
http://www.gnu.org/software/sed/manual/html_node/The-_0022s_0022-Command.html
http://sed.sourceforge.net/sedfaq3.html#s3.1.3
If you want to replace only cat by dog only if part of the word matches :
$ perl -pe 's/cat(?=.)/dog/' file.txt
cat
dogch
dog_mouse
dogty
I use Positive Look Around, see http://www.perlmonks.org/?node_id=518444
If you really want sed :
sed '/^cat$/!s/cat/dog/' file.txt
bash-3.00$ cat t
cat
catch
cat_mouse
catty
To replace cat only if it is part of a string
bash-3.00$ sed 's/cat\([^$]\)/dog\1/' t
cat
dogch
dog_mouse
dogty
To replace all occurrences of cat:
bash-3.00$ sed 's/cat/dog/' t
dog
dogch
dog_mouse
dogty
awk solution for this
awk '{gsub("cat","dog",$0); print}' temp.txt

sed/awk: replace N occurrence

it's possible to change N (for example second occurrence) in file using one-line sed/awk except such method?:
line_num=`awk '/WHAT_TO_CHANGE/ {c++; if (c>=2) {c=NR;exit}}END {print c}' INPUT_FILE` && sed "$line_num,$ s/WHAT_TO_CHANGE/REPLACE_TO/g" INPUT_FILE > OUTPUT_FILE
Thanks
To change the Nth occurence in a line you can use this:
$ echo foo bar foo bar foo bar foo bar | sed 's/foo/FOO/2'
foo bar FOO bar foo bar foo bar
So all you have to do is to create a "one-liner" of your text, e.g. using tr
tr '\n' ';'
do your replacement and then convert it back to a multiline again using
tr ';' '\n'
This awk solution assumes that WHAT_TO_CHANGE occurs only once per line. The following replaces the second 'one' with 'TWO':
awk -v n=2 '/one/ { if (++count == n) sub(/one/, "TWO"); } 1' file.txt