如何使用 vi 或 sed 替换指定字符串之间多次出现的字符?

How do I replace multiple occurrence of a character between specified strings using vi or sed?

我正在尝试仅在“,”出现在 65510 和“i”之间时替换它:

2001::/32,fd00::230:5,0,100,0,65510,6939,i

2001:200:900::/40,fd00::230:5,0,100,0,65510,6939,2516,7660,7660,7660,i

所以期望的输出是:

2001::/32,fd00::230:5,0,100,0,65510_6939,i

2001:200:900::/40,fd00::230:5,0,100,0,65510_6939_2516_7660_7660_7660,i

我试过以下方法:

sed -e 's/,\([^65510]*\)i/_i/' input-file.txt

但是输出只替换了最后一个",":

2001::/32,fd00::230:5,0,100,0,65510,6939_i

2001:200:900::/40,fd00::230:5,0,100,0,65510,6939,2516,7660,7660,7660_i

您能否尝试在 GNU awk.

中使用显示的示例进行跟踪、编写和测试
awk '
match([=10=],/65510.*i/){
  val=substr([=10=],RSTART,RLENGTH)
  gsub(/,/,"_",val)
  print substr([=10=],1,RSTART-1) val substr([=10=],RSTART+RLENGTH)
}
' Input_file

更通用的答案: 如果 i 出现不止一次,它可能会出现也可能不会出现在像 65510....i....i 这样的系列中,然后可以尝试以下。

awk '
{
  line=""
  while(match([=11=],/65510[^i]*/)){
    val=substr([=11=],RSTART,RLENGTH+1)
    gsub(/,/,"_",val)
    line=(line?line:"")substr([=11=],1,RSTART-1) val
    [=11=]=substr([=11=],RSTART+RLENGTH+1)
  }
  if([=11=]!=""){
    line=line [=11=]
  }
  print line
}
'  Input_file

这可能适合您 (GNU sed):

sed -E '/\b65510\b.*i/{:a;s/\b(65510\b[^,i]*),([^i])/_/;ta}' file

如果当前行包含 65510,然后是 i,请将它们之间的任何 , 替换为 _

对于 sed 你可以使用一个循环:

[STEP 101] $ cat file
2001::/32,fd00::230:5,0,100,0,65510,6939,i
2001:200:900::/40,fd00::230:5,0,100,0,65510,6939,2516,7660,7660,7660,i
[STEP 102] $
[STEP 103] $ sed -e :loop -e 's/\(,65510.*\),\(.*,i\)/_/; tloop' file
2001::/32,fd00::230:5,0,100,0,65510_6939,i
2001:200:900::/40,fd00::230:5,0,100,0,65510_6939_2516_7660_7660_7660,i
[STEP 104] $
[STEP 105] $ # if your sed supports -E it can be a bit simpler --
[STEP 106] $ sed -E -e :loop -e 's/(,65510.*),(.*,i)/_/; tloop' file
2001::/32,fd00::230:5,0,100,0,65510_6939,i
2001:200:900::/40,fd00::230:5,0,100,0,65510_6939_2516_7660_7660_7660,i

关于t命令:

tlabel

If a s/// has done a successful substitution since the last input line was read and since the last t or T command, then branch to label; if label is omitted, branch to end of script.


您可以使用酷炫的 sed 调试工具 sedsed 看看它是如何工作的:

使用 GNU awk 将第 3 个参数匹配 () 和 gensub():

$ awk 'match([=10=],/(.*,)(65510.*)(,i.*)/,a){[=10=]=a[1] gensub(/,/,"_","g",a[2]) a[3]} 1' file
2001::/32,fd00::230:5,0,100,0,65510_6939,i

2001:200:900::/40,fd00::230:5,0,100,0,65510_6939_2516_7660_7660_7660,i

上面只是将每个输入行分成 3 部分([=11= 之前的逗号部分),65510 到 [=13= 之前的逗号之前的字符的部分,以及该行的其余部分)然后将中间部分的每个 , 替换为 _ 并将这 3 个部分重新粘贴在一起以形成修改后的输出行。如果输入行不包含 ,65510.*,i 则保持原样。

如果 ,65510x,.*,i(其中 x 是不是逗号的任何字符)可以存在于您的输入中,它将失败 - 如果可能发生,则将其包含在您问题的示例中。

你可以试试 Perl

$ cat lou.txt
2001::/32,fd00::230:5,0,100,0,65510,6939,i
2001:200:900::/40,fd00::230:5,0,100,0,65510,6939,2516,7660,7660,7660,i
$ perl -pe ' s/65510\K(.+?)(?=,i)/$c=;$c=~s!,!_!g;$c/ge ' lou.txt
2001::/32,fd00::230:5,0,100,0,65510_6939,i
2001:200:900::/40,fd00::230:5,0,100,0,65510_6939_2516_7660_7660_7660,i
$

$ perl -pe ' s/(?=65510)(.+?)(?=,i)/$c=;$c=~s!,!_!g;$c/ge ' lou.txt
2001::/32,fd00::230:5,0,100,0,65510_6939,i
2001:200:900::/40,fd00::230:5,0,100,0,65510_6939_2516_7660_7660_7660,i
$

如果你确定65510之前总会有一个逗号,那么

$ perl -pe ' s/,(?=65510)(.+?)(?=,i)/$c=;$c=~s!,!_!g;sprintf(",%s",$c)/ge ' lou.txt
2001::/32,fd00::230:5,0,100,0,65510_6939,i
2001:200:900::/40,fd00::230:5,0,100,0,65510_6939_2516_7660_7660_7660,i
$

如果65510两边用逗号包裹,则

$ perl -pe ' s/,(?=65510,)(.+?)(?=,i)/$c=;$c=~s!,!_!g;sprintf(",%s",$c)/ge ' lou.txt
2001::/32,fd00::230:5,0,100,0,65510_6939,i
2001:200:900::/40,fd00::230:5,0,100,0,65510_6939_2516_7660_7660_7660,i
$