gnu sed 如何执行 bash 函数将计数器值增加为
$ k(){ ((i++)); echo $i ;} ;export -f k
$ i=; echo -e 'oAo\nooAo\nAo' | sed -E '/A/e k'
1
oAo
1
ooAo
1
Ao
失败而不是正确地进行计数器
答案1
进程不能影响其父进程的环境。当您使用 sed 的 e
命令时,它会分叉一个新的 shell 来运行该命令(它继承导出的k
函数),并且对该 shell 的变量所做的任何更改i
都会在它终止时丢失(即 k 函数退出时)。
如果你想增加一个变量,你必须将它存储在环境之外的某个地方。例如在一个文件中。如果您不想使用文件,则没有理由不能使用类似memcached
或redis
或 SQL 数据库之类的东西。
例如:
k() {
local counterfile i
counterfile='/tmp/counter.i'
[ -e "$counterfile" ] && i=$(cat "$counterfile")
((i++))
echo "$i" | tee "$counterfile"
}
export -f k
当您现在运行它时,计数器会递增:
$ rm /tmp/counter.i
$ printf 'oAo\nooAo\nAo' | sed -E '/A/e k'
1
oAo
2
ooAo
3
Ao
$ echo 100 > /tmp/counter.i
$ printf 'oAo\nooAo\nAo' | sed -E '/A/e k'
101
oAo
102
ooAo
103
Ao
顺便说一句,虽然 sed 的e
命令很有用,但我认为 perl 对于您正在做的事情来说是一种更好的语言。例如
$ printf 'oAo\nooAo\nAo' |
perl -lpe 'BEGIN { $i=shift || 0 };
if (/A/) {print ++$i}' 10
11
oAo
12
ooAo
13
Ao
如果您不提供参数,则$i
默认为零。
如果您希望脚本看起来更像“sed”,有很多方法可以$i
在包含 的行上递增和打印计数器变量A
。这是另一个:
/A/ && print ++$i
如果每行可能有多个匹配项,并且您需要在每个匹配项上递增并打印计数器,那么您需要迭代匹配项。例如
$ printf 'oAo\noAoAoAo\nAo' |
perl -lpe 'BEGIN { $i=shift || 0 };
for (/oA/g) {print ++$i}'
1
oAo
2
3
4
oAoAoAo
Ao
或者,如果您不需要累计总数,而只需要计算每行上的匹配项:
$ printf 'oAo\noAoAoAo\nAo' |
perl -lne '$c = () = $_ =~ /oA/g;
printf "%02i:%s\n", $c, $_'
01:oAo
03:oAoAoAo
00:Ao
最后一个可能需要一些解释。阅读它向后,该$c = () = $_ =~ /oA/g
语句首先执行正则表达式匹配/oA/g
,并将列表上下文中的结果返回到空列表()
,然后将其分配给变量$c
。因为$c
是标量变量,而不是数组/列表,所以这是在标量上下文中计算的,因此返回该列表中的元素数量。这是一个相当常见的 Perl 习惯用法,用于计算匹配数。
注意:perl 的-p
选项使其运行方式与 sed 非常相似(即迭代其输入并在处理后打印每一行,除非语句阻止打印)。 Perl 的-n
选项使其运行起来很像sed -n
(迭代输入,仅打印明确指示打印的内容)。
最后,值得注意的是,这个 sed 和 bash 函数版本会分叉bash
, cat
, 和tee
对于每个匹配的输入行/A/
。 Perl 版本不分叉任何内容,它只是迭代其输入。另外值得注意的是,您必须在 bash 中 fork 一个外部程序才能完成的许多事情可以通过 Perl 使用其自己的内置语法(或数千个库模块之一)在内部完成
答案2
当您使用 GNU sed 时,您无需求助于用户定义的函数即可获得所需的结果。这里我们利用保存空间来保存递增计数。
echo -e 'oAo\nooAoAoA\no' |
sed -En "/A/!d
p;:a
H;g
s/^(\n*).*/expr '\1' : '.*'/ep
g;s//\1/;x;s/^\n*//;s/A//;//ba
"
输出:
oAo
1
ooAoAoA
2
3
4