1

File:

1
2
3
4
1
5
6
7
4

I would like to search for a string, in this case 1, and then change the next string of 4 to 8.
Expected output:

1
2
3
8
1
5
6
7
4

I've tried:

cat file | sed '/1/ s/4/8/'

But that only looks for a string to change in that line.
I also can't use line number to replace in my original file because there might be a different number of lines between the first string and the second.
I do not have GNU sed installed.

  • 2
    if the first occurence of 4 has to be changed you don't need anything sed "/4/8/" file that's all /foo/ s/bar/foo/ means on line with foo .... substitute.... – francois P Jan 22 '18 at 21:29
  • 2
    What if there's another 1 further down which is followed by another 4 ? Do you need to replace only the 1st 4 in the file which occurs after a 1 or do you need to repeat that for each 1st 4 that follows after a 1 ? – don_crissti Jan 22 '18 at 21:36
  • Yes I edited the file and expected output. Sorry – user1712037 Jan 22 '18 at 21:46
  • @user1712037,how about awk solution? – RomanPerekhrest Jan 22 '18 at 21:49
  • Sure. What would the awk syntax look like? – user1712037 Jan 22 '18 at 21:51
2

The POSIX-specified file editor, ex, is capable of doing exactly that.

printf '%s\n' '/1//4/s//8/' x | ex file.txt

ex is capable of combining multiple addresses. So /1/ means "Go to" (or refer to) "the next line matching regex 1." Then /4/ goes from that line to the next line matching 4. And s//8/ has the usual meaning; as in Sed, a blank regex passed to the s command means "reuse last regex used" which in this case is 4.

To print the modified file but not save the changes, use the following command instead:

printf '%s\n' '/1//4/s//8/' %p | ex file.txt

Just to give the idea of multiple addresses better, the following command deletes the first line containing cherry before the first line containing banana after line 27:

printf '%s\n' '27/banana/?cherry?d' x | ex file.txt

x means to save changes and exit, and %p means "print whole file." (% is a synonym for 1,$, which is an address range from the first line to the last line.)

1

To replace only the 1st PATTERN that occurs after a MARKER you could do:

sed '/MARKER/,${
/PATTERN/{
x
//{
x
b
}
g
s/PATTERN/REPLACEMENT/
}
}' infile

Use a range (from the 1st MARKER to the end of file) and the hold buffer. Each time you encounter a line matching PATTERN you exchange buffers and check if the line that was in hold space matches too: if it does, then exchange back and go to end of script; else overwrite with current line and replace.

1

generic solution using awk, consider the following modified input file with multiple 1s and 4s

$ cat ip.txt
1
foo
1
xyz
4
4
1
1
eeeee
4
1
rrrrrr
4
1
4

Use a flag to indicate that 1 was matched and a counter to know which block is being modified. Clearing the flag is needed to start the matching cycle again

$ # first block
$ awk '/1/{f=1} f && /4/{c++; f=0; if(c==1) $0="8"} 1' ip.txt
1
foo
1
xyz
8
4
1
1
eeeee
4
1
rrrrrr
4
1
4

$ # second block
$ awk '/1/{f=1} f && /4/{c++; f=0; if(c==2) $0="8"} 1' ip.txt
1
foo
1
xyz
4
4
1
1
eeeee
8
1
rrrrrr
4
1
4

can be simplified for changing only first block

awk '/1/{f=1} f && !c && /4/{c++; $0="8"} 1' ip.txt
0

Awk solution:

awk '$1==1{ f++ }f==1 && $1==4{ $1=8 }1' file

The output:

1
2
3
8
1
5
6
7
4
  • awk '$1==1{ f++ }f==1 && $1==4{ $1=8 }1' num.txt awk: syntax error near line 1 awk: bailing out near line 1 – user1712037 Jan 22 '18 at 22:28
  • @user1712037, it can not be so. Check if you run this command as standalone, not as part of some pipeline or else. Or ... you are not on Linux system – RomanPerekhrest Jan 22 '18 at 22:31
  • 1
    This won't work if there are multiple lines matching 1 before the 1st 4 that follows... – don_crissti Jan 22 '18 at 23:07
  • @don_crissti, I don't see such input sample - share some link to the text fragment you mentioned – RomanPerekhrest Jan 23 '18 at 8:25
0

With awk it is more easy and compatible to any Nth occurrence needed

awk '/pattern to search/{n+=1}{if (n==OCCURRENCE){sub("PATTERN","SUBSTITUTE",$0)};print }' FILE-NAME

example :

-bash-4.4$ cat toto
1
2
3
4
5
6
1
2
3
4
5
6
1
2
3
4
-bash-4.4$ awk '/4/{n+=1}{if (n==2){sub("4","---8",$0)};print }' toto
1
2
3
4
5
6
1
2
3
---8
5
6
1
2
3
4
-bash-4.4$ 

only the second 4 is changed but not the first or the last.

  • 1
    but this is not even checking if 1 occurred first... – Sundeep Jan 23 '18 at 4:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.