我需要更改文本文件每一行上子字符串的一些实例。这些实例都是连续的,例如 3 到 6 或 2 到 5 等。此操作需要什么命令?我得到的最接近的是:
sed 's/this/that/3' file1
这只改变了第三次出现的情况。我希望有类似的东西
sed 's/this/that/3,6' file1
可能是答案,但sed
没有出现范围。
输入示例:
I want to change all letters "a" to "w" starting from the word "all" until the second "all" (inclusive)
预期输出:
I want to change wll letters "w" to "w" stwrting from the word "wll" until the second "all" (inclusive)
答案1
和perl
:
将第三到第六a
s改为b
s:
$ echo aaaaaaaaa | perl -pe '$n=0; s{a}{++$n;$n==3..$n==6?"b":$&}ge'
aabbbbaaa
这使用了运算符e
的标志s{regex}{replacement}flags
,因此替换被评估为代码,"b"
如果递增的计数器在 3 到 6 之间,则给出该代码,$&
否则将给出匹配的值 ( )。或者:
$ echo aaaaaaaa | perl -pe '$n=0; s/a(?(?{++$n; $n == 3 .. $n == 6})|(*FAIL))/b/g'
aabbbbaa
使用(?(condition)yes|no)
正则表达式运算符,该运算符给出(*FAIL)
递增计数器是否不在 3 .. 6 范围内的信息。
GNUsed
支持s/foo/bar/3g
替换第三次及之后出现的foo
.
因此,对于这样的固定字符串,您可以这样做:
$ echo aaaaaaaaa | sed 's/a/\n/3g;s/\n/a/5g;s/\n/b/g'
aabbbbaaa
也就是说,将倒数第 3个a
换行符替换为换行符(保证在模式空间中不会出现其他情况),然后将倒数第5 个换行符恢复回s (6 - 3 + 1 = 4 是我们想要的 sa
数量)a
替换,因此恢复后面的那些),然后用b
s 替换所有剩余的换行符。
与任何sed
:
sed 's/a/\
/g
s/\n/b/3
s/\n/b/3
s/\n/b/3
s/\n/b/3
s/\n/a/g'
要将的前两次出现之间的s更改a
为s :w
all
$ echo aaallaaallaaa | perl -pe 's{all.*?all}{$& =~ s/a/w/gr}e'
aawllwwwllaaa
\b
如果all
必须分隔,请使用字边界运算符字。
$ echo 'alloy (all-hands aaa ball all) fall' | perl -pe 's{all.*?all}{$& =~ s/a/w/gr}e'
wlloy (wll-hands aaa ball all) fall
$ echo 'alloy (all-hands aaa ball all) fall' | perl -pe 's{\ball\b.*?\ball\b}{$& =~ s/a/w/gr}e'
alloy (wll-hwnds www bwll wll) fall
(添加-Mopen=locale
要根据区域设置字符映射进行解码的字符,而不是假设它们只是 ASCII,例如将 UTF-8 编码allée
为一个法语单词,并且all
后面不跟一些非单词字符)。
答案2
这对于 来说可能是不可能的sed
,但是下面的 GNUawk
程序可以工作:
awk -v frst=2 -v lst=5 '{for (i=1; i<=(lst-frst+1);i++) $0=gensub(/a/,"w",frst)}1'
- 这会将第一次和最后一次出现的情况传递为变量
frst
和lst
来替换awk
。 - 然后它将用于
gensub()
替换frst
行上第 -th 次出现的搜索模式,并将结果分配回当前行缓冲区。 - 这将总共执行 (
lst-frst+1
) 次,以替换所有所需的事件。然后,打印当前行(包括所有修改)。 - 请注意,要替换的字符串中出现的次数始终保持不变,因为已替换的出现次数不再计入下一次循环迭代中。
应用示例:
$ echo "a1a2a3a4a5a6" | awk -v frst=2 -v lst=5 '{for (i=1; i<=(lst-frst+1);i++) $0=gensub(/a/,"w",frst)}1'
a1w2w3w4w5a6
或你原来的:
$ echo 'I want to change all letters "a" to "w" starting from the word "all" until the second "all" (inclusive)' | awk -v frst=3 -v lst=6 '{for (i=1; i<=(lst-frst+1);i++) $0=gensub(/a/,"w",frst)}1'
I want to change wll letters "w" to "w" stwrting from the word "wll" until the second "all" (inclusive)
与往常一样,如果搜索模式可以重叠,则这将无法按预期工作。
答案3
对于任何 awk:
awk -v FS='a' -v start=3 -v end=6 -v replace="w" '
{
for(i=1; i<NF; i++)
printf("%s", $i (start<=i && i<=end? replace: FS))
print $NF
}' infile
在这里,FS=a
我们告诉 awk 根据字符分割记录a
;我们还将其他 awk 变量定义为开始,结尾和代替分别为目标字符“a”的开始和结束位置,我们将其替换为“w”字符。
然后我们循环遍历字段并打印字段本身,如果字段编号在起始点和结束点之间,则打印字段本身,如果不在该范围内,则打印字符“a”。最后我们也输出最后一个字段。
答案4
使用乐(以前称为 Perl_6)
~$ echo aaaaaaaa | perl6 -pe 's:nth(3..6)/a/b/;'
aabbbbaa
Raku(Perl 系列中的一种编程语言)有一个新的nth
正则表达式修饰符(“位置副词”),它支持1st
、2nd
、3rd
等同义词。对于一般替换用途nth()
,插入数字或范围参数。
以上是快速版本。下面只是为了演示 Raku 正则表达式修饰符在这方面的直观性(6th
、5th
、4th
、3rd
匹配项被连续替换,每次用于andthen
重新加载$_
主题变量):
~$ echo aaaaaaaa | perl6 -ne 'S:6th/a/b/ andthen S:5th/a/b/ andthen S:4th/a/b/ andthen S:3rd/a/b/ andthen .put;'
aabbbbaa
https://docs.raku.org/language/regexes#Positional_adverbs
https://raku.org