查找一个字符串并在找到第一个字符串后替换另一个字符串

查找一个字符串并在找到第一个字符串后替换另一个字符串

文件:

1
2
3
4
1
5
6
7
4

我想搜索一个字符串,在本例中为1,然后将下一个字符串更改48
预期输出:

1
2
3
8
1
5
6
7
4

我试过了:

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

但这只是寻找该行中要更改的字符串。
我也无法使用行号来替换原始文件,因为第一个字符串和第二个字符串之间可能有不同的行数。
我没有安装 GNU sed。

答案1

POSIX 指定的文件编辑器,ex,正是能够做到这一点。

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

ex能够组合多个地址。因此/1/意味着“转到”(或引用)“匹配 regex 的下一行” 1。然后/4/从那条线到下一行匹配4。并s//8/具有通常的含义;与 Sed 中一样,传递给s命令的空白正则表达式意味着“重用最后使用的正则表达式”,在本例中为4.

要打印修改后的文件但不保存更改,请改用以下命令:

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

为了更好地理解多个地址,以下命令删除包含的第一行cherry 第一行包含banana 第 27 行:

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

x表示保存更改并退出,%p表示“打印整个文件”。 (%是 的同义词1,$,它是从第一行到最后一行的地址范围。)

答案2

要仅替换PATTERNa 之后出现的第一个,MARKER您可以执行以下操作:

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

使用范围(从文件第一个MARKER到文件末尾)和保留缓冲区。每次遇到匹配的行时,PATTERN 您都会交换缓冲区并检查保留空间中的行是否也匹配:如果匹配,则交换回来并转到脚本末尾;否则用当前行覆盖并替换。

答案3

使用通用解决方案awk,请考虑以下带有多个1s 和4s 的修改后的输入文件

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

使用标志来指示1匹配,并使用计数器来了解哪个块正在被修改。需要清除标志才能再次开始匹配循环

$ # 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

可以简化为仅更改第一个块

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

答案4

使用 awk,它更容易并且兼容任何需要的第 N 次出现

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

例子 :

-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$ 

仅更改了第二个 4,而不更改了第一个或最后一个。

相关内容