文件:
1
2
3
4
1
5
6
7
4
我想搜索一个字符串,在本例中为1
,然后将下一个字符串更改4
为8
。
预期输出:
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
要仅替换PATTERN
a 之后出现的第一个,MARKER
您可以执行以下操作:
sed '/MARKER/,${
/PATTERN/{
x
//{
x
b
}
g
s/PATTERN/REPLACEMENT/
}
}' infile
使用范围(从文件第一个MARKER
到文件末尾)和保留缓冲区。每次遇到匹配的行时,PATTERN
您都会交换缓冲区并检查保留空间中的行是否也匹配:如果匹配,则交换回来并转到脚本末尾;否则用当前行覆盖并替换。
答案3
使用通用解决方案awk
,请考虑以下带有多个1
s 和4
s 的修改后的输入文件
$ 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,而不更改了第一个或最后一个。