我只想替换k
单词的第一个实例。
我怎样才能做到这一点?
例如。说文件foo.txt
包含单词'linux'的100个实例。
我只需要替换前50个事件。
我只想替换k
单词的第一个实例。
我怎样才能做到这一点?
例如。说文件foo.txt
包含单词'linux'的100个实例。
我只需要替换前50个事件。
Answers:
下面的第一部分描述了sed
用于更改一行中的前k个出现次数的方法。第二部分扩展了此方法,以仅更改文件中的前k个出现,而不管它们出现在哪一行上。
使用标准sed,有一个命令来替换一行中单词的第k次出现。如果k
为3,例如:
sed 's/old/new/3'
或者,您可以将所有事件替换为:
sed 's/old/new/g'
这些都不是您想要的。
GNU sed
提供了一个扩展,它将更改第k个出现次数,并且此后都会更改。例如,如果k为3:
sed 's/old/new/g3'
这些可以组合起来做您想要的。要更改前3次出现:
$ echo old old old old old | sed -E 's/\<old\>/\n/g4; s/\<old\>/new/g; s/\n/old/g'
new new new old old
这里的\n
位置很有用,因为我们可以确保它永远不会在线发生。
我们使用三个sed
替换命令:
s/\<old\>/\n/g4
这是GNU扩展,用于替换old
带有的第四个和所有后续出现的\n
。
扩展的正则表达式功能\<
用于匹配单词的开头和单词\>
的结尾。这样可以确保只匹配完整的单词。扩展的正则表达式需要-E
选择sed
。
s/\<old\>/new/g
仅old
剩余的前三个出现,并用替换它们new
。
s/\n/old/g
在第一步中,将第四个和所有剩余的出现old
替换为\n
。这使它们恢复到原始状态。
如果GNU sed不可用,并且您想要将的前3次出现更改old
为new
,则使用以下三个s
命令:
$ echo old old old old old | sed -E -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'
new new new old old
当k
数量较少时效果很好,但不能很好地放大k
。
由于某些非GNU sed不支持将命令与分号结合使用,因此此处引入的每个命令都有其自己的-e
选项。可能还需要验证您是否sed
支持单词边界符号\<
和\>
。
我们可以告诉sed读取整个文件,然后执行替换。例如,要替换old
使用BSD样式sed 的前三个事件:
sed -E -e 'H;1h;$!d;x' -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'
sed命令H;1h;$!d;x
读取整个文件。
因为上面没有使用任何GNU扩展,所以它应该可以在BSD(OSX)sed上运行。注意,请注意,这种方法要求sed
可以处理长行。GNU sed
应该没问题。使用非GNU版本的用户sed
应该测试其处理长行的能力。
使用GNU sed,我们可以进一步使用上述g
技巧,但将其\n
替换为\x00
,以替换前三个事件:
sed -E -e 'H;1h;$!d;x; s/\<old\>/\x00/g4; s/\<old\>/new/g; s/\x00/old/g'
随着规模的扩大,这种方法可以很好地扩展k
。但是,这假定\x00
您的原始字符串中没有该字符串。由于不可能将字符\x00
放在bash字符串中,因此通常是一个安全的假设。
tr '\n' '|' < input_file | sed …
。但是,当然,这会将整个输入转换为一行,并且某些非GNU sed无法处理任意长行。(2)您说,“…上面,带引号的字符串'|'
应替换为任何字符或字符串,…”,但是您不能tr
用来用长度大于1的字符串替换字符。(3)在最后一个示例中,您说-e 's/\<old\>/new/' -e 's/\<old\>/w/' | tr '\000' '\n'\>/new
。这似乎是的错字-e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/' | tr '\000' '\n'
。
awk命令可用于用替换替换单词的前N个出现的位置。
仅当单词完全匹配时,命令才会替换。
在下面的例子中,我代替第一27
出现的old
与new
使用子
awk '{for(i=1;i<=NF;i++){if(x<27&&$i=="old"){x++;sub("old","new",$i)}}}1' file
该命令循环遍历每个字段,直到匹配为止
old
,它检查计数器是否在27以下,递增并替换行上的第一个匹配项。然后移至下一个字段/行并重复。
手动替换字段
awk '{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file
与之前的命令相似,但由于它已经在哪个字段上有一个标记
($i)
,它只是将字段的值从更改old
为new
。
之前进行检查
awk '/old/&&x<27{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file
检查行中是否包含旧行,以及计数器是否低于27,
SHOULD
可以提供较小的速度提升,因为如果行为假,它将不处理行。
结果
例如
old bold old old old
old old nold old old
old old old gold old
old gold gold old old
old old old man old old
old old old old dog old
old old old old say old
old old old old blah old
至
new bold new new new
new new nold new new
new new new gold new
new gold gold new new
new new new man new new
new new new new dog new
new new old old say old
old old old old blah old
假设您只想替换字符串的前三个实例...
seq 11 100 311 |
sed -e 's/1/\
&/g' \ #s/match string/\nmatch string/globally
-e :t \ #define label t
-e '/\n/{ x' \ #newlines must match - exchange hold and pattern spaces
-e '/.\{3\}/!{' \ #if not 3 characters in hold space do
-e 's/$/./' \ #add a new char to hold space
-e x \ #exchange hold/pattern spaces again
-e 's/\n1/2/' \ #replace first occurring '\n1' string w/ '2' string
-e 'b t' \ #branch back to label t
-e '};x' \ #end match function; exchange hold/pattern spaces
-e '};s/\n//g' #end match function; remove all newline characters
注意:以上内容可能不适用于嵌入注释
...或者在我的示例中为'1'...
22
211
211
311
在那里,我使用两种著名的技术。首先1
,用替换行中每次出现的\n1
。这样,当我接下来进行递归替换时,如果替换字符串包含替换字符串,就可以确定不会两次替换出现的位置。例如,如果我替换he
为hey
它仍然可以工作。
我这样做:
s/1/\
&/g
其次,我通过h
在每次出现的旧空间中添加一个字符来计算替换次数。一旦达到三个,就不会再发生了。如果将其应用于数据并将\{3\}
替换为所需的总替换项,并将/\n1/
地址更改为要替换的内容,则仅应替换任意数量的替换项。
我只是-e
为了可读性而做了所有的事情。POSIXly可以这样写:
nl='
'; sed "s/1/\\$nl&/g;:t${nl}/\n/{x;/.\{3\}/!{${nl}s/$/./;x;s/\n1/2/;bt$nl};x$nl};s/\n//g"
和GNU sed
:
sed 's/1/\n&/g;:t;/\n/{x;/.\{3\}/!{s/$/./;x;s/\n1/2/;bt};x};s/\n//g'
还要记住,它sed
是面向行的-它不会读入整个文件,然后像其他编辑器一样,尝试遍历该文件。sed
简单高效。也就是说,执行以下操作通常很方便:
这是一个小shell函数,将其捆绑成一个简单执行的命令:
firstn() { sed "s/$2/\
&/g;:t
/\n/{x
/.\{$(($1))"',\}/!{
s/$/./; x; s/\n'"$2/$3"'/
b t
};x
};s/\n//g'; }
因此,我可以这样做:
seq 11 100 311 | firstn 7 1 5
...并得到...
55
555
255
311
...要么...
seq 10 1 25 | firstn 6 '\(.\)\([1-5]\)' '\15\2'
...要得到...
10
151
152
153
154
155
16
17
18
19
20
251
22
23
24
25
...或者,为了与您的示例相匹配(幅度较小):
yes linux | head -n 10 | firstn 5 linux 'linux is an os kernel'
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux
linux
linux
linux
linux
使用shell循环和ex
!
{ for i in {1..50}; do printf %s\\n '0/old/s//new/'; done; echo x;} | ex file.txt
是的,这有点愚蠢。
;)
注意:如果old
文件中的实例少于50个,此操作可能会失败。(我尚未测试过。)如果这样,它将使文件保持不变。
更好的是,使用Vim。
vim file.txt
qqgg/old<CR>:s/old/new/<CR>q49@q
:x
说明:
q # Start recording macro
q # Into register q
gg # Go to start of file
/old<CR> # Go to first instance of 'old'
:s/old/new/<CR> # Change it to 'new'
q # Stop recording
49@q # Replay macro 49 times
:x # Save and exit
一个简单但不是很快的解决方案是遍历/programming/148451/how-to-use-sed-to-replace-only-the-first-occurrence-in-a中描述的命令 -文件
for i in $(seq 50) ; do sed -i -e "0,/oldword/s//newword/" file.txt ; done
如果newword不属于oldword,则此特定的sed命令可能仅适用于GNU sed 。对于非GNU sed,请参见此处如何仅替换文件中的第一个模式。
随着GNU awk
您可以将记录分隔符设置RS
到要被替换的字由字边界分隔。然后是在输出上将记录分隔符设置为第一k
条记录的替换词,而保留其余部分的原始记录分隔符的情况
awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, NR <= limit? replacement: RT}' file
要么
awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, limit--? replacement: RT}' file