2

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.

user1712037
  • 141
  • 1
  • 2
  • 9

5 Answers5

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.)

Wildcard
  • 35,316
  • 26
  • 130
  • 258
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.

don_crissti
  • 79,330
  • 30
  • 216
  • 245
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
Sundeep
  • 11,753
  • 2
  • 26
  • 57
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
RomanPerekhrest
  • 29,703
  • 3
  • 43
  • 67
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.

ilkkachu
  • 133,243
  • 15
  • 236
  • 397
francois P
  • 1,219
  • 11
  • 27