使用 sed 或 perl 枚举文件中的所有匹配项

使用 sed 或 perl 枚举文件中的所有匹配项

我有下一个文件:

:~$ cat test
        <Set Id="16">
                <Ver="44" Sniff="no" B2vpnId="" Collision="no">
        </Set>        
        <Set Id="17">
                <Ver="22" Sniff="no" B2vpnId="" Collision="no">
        </Set>

我需要增加文件中的 B2vpnId 值,我找到了一种在输出中获取此更改的方法:

:~$ i=1;while read -r line; do echo "$line"|sed -e 's/B2vpnId=""/B2vpnId="'"vpn$i"'"/';((i+=1));done <<< "$(cat test|grep B2vpnId=)"
<Ver="44" Sniff="no" B2vpnId="vpn1" Collision="no">
<Ver="22" Sniff="no" B2vpnId="vpn2" Collision="no">

但由于我有许多其他配置行,我需要在文件本身中进行此更改,但找不到方法来做到这一点。

答案1

使用 的perl/e则表达式修饰符将运算符的替换部分评估s/search/replace/为 Perl 代码:

$ perl -p -e 's/B2vpnId=""/sprintf "B2vpnId=\"vpn%i\"", ++$i/e' test 
        <Set Id="16">
                <Ver="44" Sniff="no" B2vpnId="vpn1" Collision="no">
        </Set>
        <Set Id="17">
                <Ver="22" Sniff="no" B2vpnId="vpn2" Collision="no">
        </Set>

如果您希望它更改磁盘上的文件(而不是仅仅输出到标准输出),请使用 perl 的-i选项。

$i如果您正在处理多个文件并希望它在每个文件后重置计数器 ( ),请添加; $i=0 if eof到脚本末尾:

$ cp test test2
$ perl -p -e 's/B2vpnId=""/sprintf "B2vpnId=\"vpn%i\"", ++$i/e; $i=0 if eof' test test2
        <Set Id="16">
                <Ver="44" Sniff="no" B2vpnId="vpn1" Collision="no">
        </Set>
        <Set Id="17">
                <Ver="22" Sniff="no" B2vpnId="vpn2" Collision="no">
        </Set>

        <Set Id="16">
                <Ver="44" Sniff="no" B2vpnId="vpn1" Collision="no">
        </Set>
        <Set Id="17">
                <Ver="22" Sniff="no" B2vpnId="vpn2" Collision="no">
        </Set>

如果没有该$i=0 if eof语句,测试的第二个副本将具有“vpn3”和“vpn4”的 B2vpnID。

顺便说一句,如果您希望计数器从不同的数字(例如 10)开始,请添加BEGIN { $i=9 };到脚本的开头。 BEGIN在读取任何输入之前,块仅执行一次。脚本的其余部分将重复执行,每个输入行执行一次。顺便说一句,还有其他类型的代码块仅执行一次 - 来自man perlsyn

当块前面有编译阶段关键字(例如BEGINENDINITCHECK或)时UNITCHECK,该块将仅在相应的执行阶段运行。请参阅perlmod了解更多详情。

记住$i会增加每次都会使用它(因为脚本使用的是 using++$i而不是$i++,这会在每次使用后递增它),因此从起始数字中减去 1。

另请记住更改$i=0 if eof以匹配 BEGIN 块,例如,$i=9 if eof如果您正在使用它。

$ perl -p -e 'BEGIN { $i=9 };
              s/B2vpnId=""/sprintf "B2vpnId=\"vpn%i\"", ++$i/e;
              $i=9 if eof' test 

或者,由于我们现在使用 BEGIN 块来初始化 $i,因此我们可以对其进行后递增(并使用另一个变量作为起始值,因此我们只需在一个位置更改它)。

$ perl -p -e 'BEGIN { $start = 10; $i = $start };
              s/B2vpnId=""/sprintf "B2vpnId=\"vpn%i\"", $i++/e;
              $i = $start if eof' test 

或者甚至将其设置为默认值 1,然后如果第一个 arg 不是现有文件,将其设置为第一个 arg :

$ perl -p -e 'BEGIN {
                $start = 1;
                $start = shift unless -f $ARGV[0];
                $i = $start
              };

              s/B2vpnId=""/sprintf "B2vpnId=\"vpn%i\"", $i++/e;

              $i = $start if eof' 10 test* 

答案2

这更适合awk

$ awk 'BEGIN { c=1 } /B2vpnId/ { sub(/vpnId="/, "vpnId=\"vpn"c,$0); c++ }1' test
        <Set Id="16">
                <Ver="44" Sniff="no" B2vpnId="vpn1" Collision="no">
        </Set>        
        <Set Id="17">
                <Ver="22" Sniff="no" B2vpnId="vpn2" Collision="no">
        </Set>

要实际更改文件(GNU awk):

$ awk -i inplace <rest of command>

注:参见猫的无用用途;

答案3

最简单的方法是将输出重定向到临时文件,然后将临时文件移动到原始文件上。

然而,我对你复杂的命令行感到有点惊讶。

i=1
while read -r line; do
    echo "$line"|sed -e 's/B2vpnId=""/B2vpnId="'"vpn$i"'"/'
    ((i+=1))
done <<< "$(cat test|grep B2vpnId=)"

一个更简单的方法是:

i=1
grep 'B2vpnId=' test |
while read -r line; do 
    echo "$line"|sed -e 's/B2vpnId=""/B2vpnId="'"vpn$i"'"/'
    ((i+=1))
done

但是,如果您还需要回显不会更改的行,则可能应该将其放入grep循环中。

i=1
while read -r line; do 
    if echo "$line" | grep -q 'B2vpnId=' ; then
        echo "$line"|sed -e 's/B2vpnId=""/B2vpnId="'"vpn$i"'"/'
        ((i+=1))
    else
        echo "$line"
    fi
done < test

这是根据您自己的解决方案进行的。

相关内容