我有下一个文件:
:~$ 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
:
当块前面有编译阶段关键字(例如
BEGIN
、END
、INIT
、CHECK
或)时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
这是根据您自己的解决方案进行的。