我有一个包含 1200 万行的 csv 文件,格式如下:
mcu_i,INIT,200,iFlash, 11593925, 88347,,0x00092684,r,0x4606b570, ok,, 32,single,op-c,0,, 0, 0, 0,
mcu_i,INIT,200,iFlash, 11593931, 88348,,0x00092678,r,0x28003801, ok,, 32,single,op-c,0,, 0, 0, 0,
我想使用以下逻辑根据第六列的值删除行: if (value >= X AND value <= Y ) => 删除行
我使用 gawk 找到了一个解决方案:
gawk -i inplace -F ',' -v s="$start_marker" -v e="$end_marker" '!($6 <= e && $6 >= s)' myfile.csv
但这需要太长时间,我想要另一个性能更好的解决方案。
谢谢
答案1
长话短说
将您的gawk
标准输出重定向到/dev/null
或通过管道将其传输到cat
将大大加速它并显着减少运行时间。
gawk -i inplace [...] myfile.csv >/dev/null
或者:
gawk -i inplace [...] myfile.csv | cat
潜入水中
虽然@RomeoNinov的回答确实会比你原来的命令运行得更快,我想解释一下为什么它更快,即使使用-i inplace
.
如果你看一下交互式缓冲与非交互式缓冲部分在gawk
信息页面,你会看到:
互动节目一般行缓冲区他们的输出(即,他们写出每一行)。非交互式程序会等待直到缓冲区已满,这可能是多行输出。
gawk
即使结果没有打印到标准输出,但当它打印到某个“就地”时,这似乎也是如此。
例子
我有一个 10 行的文件。
$ cat somefile
1
2
3
4
5
6
7
8
9
10
默认情况下(不对文件进行任何更改,只是按原样打印回所有行),请注意,strace
显示gawk
运行 10 个write
系统调用 - 原始文件中的每一行一个。
$ strace -e trace=write -c gawk -i inplace 1 somefile
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00 0.000098 9 10 write
------ ----------- ----------- --------- --------- ----------------
100.00 0.000098 9 10 total
那是因为它是交互式运行,结果是行缓冲(gawk
即使结果被写入文件而不是标准输出,也会在每一行完成后立即打印)。
现在,如果我将 stdout 重定向到/dev/null
(或只是将命令通过管道传输到命令cat
)以使该命令成为非交互式,strace
则显示gawk
仅调用单个write
系统调用。这是因为它不会立即打印每一行,而是仅在缓冲区已满时才刷新结果。
$ strace -e trace=write -c gawk -i inplace 1 somefile > /dev/null
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00 0.000020 20 1 write
------ ----------- ----------- --------- --------- ----------------
100.00 0.000020 20 1 total
当然,这是累积的,输入文件越大,交互式和非交互式运行之间的差异就越大。
概括
您的命令速度很慢,因为gawk
在交互模式下,一旦完成处理,就会将每一行写入文件。这意味着它对文件执行数百万次写入。
@RomeoNinov 的解决方案比原始命令更快,因为inplace
它不使用 ,而是将输出重定向到临时文件,因此它以非交互模式运行,这优化了缓冲区刷新并使gawk
对文件执行更少的写入操作。
但是,您仍然可以使用问题中提供的命令,但只需将其 stdout 重定向到/dev/null
(因为它无论如何都是空的)或将其通过管道传输到cat
,它就会运行得一样快。
gawk
使用with的安全隐患inplace
虽然我不完全同意 @RomeoNinov 的评论,即就地操作可能会导致不可预测的结果,但请注意 @OlivierDulac 的评论这提供了一个有用的答案,解释了为什么通常使用-i inplace
被认为是安全漏洞,并且如何解决这个问题以安全的方式运行它。
答案2
一种可能的方法(通过重写命令)是:
gawk -F, -v s="$start_marker" -v e="$end_marker" '$6 > e || $6 < s' myfile.csv >/tmp/newfile
在 中awk
,不建议使用就地操作,它具有安全隐患。此外,在 100% 确定脚本正确之前,您可能会弄乱源文件。
答案3
如果不需要awk
,那么你可以尝试 Perl:
#!/usr/bin/perl
use 5.18.2;
use warnings;
use strict;
my ($X, $Y) = (88347, 88347);
while (<>) {
next
if (/(?:^[^,]*,){5}\s*([^,]+)/ && $1 >= $X && $1 <= $Y);
print;
}
正则表达式会跳过一行中的前五个逗号分隔字段,然后忽略空格,将第 6 个字段的其余部分捕获到$1
.如果条件匹配,则忽略该行;否则就是输出。
例如,它将输出值为 88348 的行。
使用像perl your_script input_file(s) > output_file
.显然输入和输出文件名应该不同!
答案4
使用乐(以前称为 Perl_6)
~$ raku -MText::CSV -e 'my @rows; my $csv = Text::CSV.new( sep => ","); \
while ($csv.getline($*IN)) -> $row { @rows.push: $row.map(*.trim) if 88000 < $row.[5] < 98000; }; \
.join(",").put for @rows;' < ~/raphui_771255.csv
Raku 是 Perl 家族的一种编程语言。它具有对 Unicode 的高级支持以及强大的正则表达式引擎。
上面的答案使用了Raku的Text::CSV
模块。 Perl(5) 模块Text::CSV_XS
很受好评,该模块的长期作者/维护者已经继续开发 Raku 的Text::CSV
模块(H. Merijn Brand,个人交流)。
输入示例(感谢@aborruso!):
mcu_i,INIT,200,iFlash, 11593925, 88347,,0x00092684,r,0x4606b570, ok,, 32,single,op-c,0,, 0, 0, 0,
mcu_i,INIT,200,iFlash, 11593931, 88348,,0x00092678,r,0x28003801, ok,, 32,single,op-c,0,, 0, 0, 0,
mcu_i,INIT,200,iFlash, 10593931, 88348,,0x00092678,r,0x28003801, ok,, 32,single,op-c,0,, 0, 0, 0,
mcu_i,INIT,200,iFlash, 21593931, 98348,,0x00092678,r,0x28003801, ok,, 32,single,op-c,0,, 0, 0, 0,
mcu_i,INIT,200,iFlash, 31593931, 108348,,0x00092678,r,0x28003801, ok,, 32,single,op-c,0,, 0, 0, 0,
示例输出:
mcu_i,INIT,200,iFlash,11593925,88347,,0x00092684,r,0x4606b570,ok,,32,single,op-c,0,,0,0,0,
mcu_i,INIT,200,iFlash,11593931,88348,,0x00092678,r,0x28003801,ok,,32,single,op-c,0,,0,0,0,
mcu_i,INIT,200,iFlash,10593931,88348,,0x00092678,r,0x28003801,ok,,32,single,op-c,0,,0,0,0,
注意:Raku 允许使用“链式”不等式。此外,Raku 没有对值进行硬编码,而是有一个特殊的关联数组%*ENV
,可用于访问 shell 变量。因此,下面从环境(即 shell)中获取 shellstartMarker
变量stopMarker
。使用高级csv( …, out => $*OUT)
函数进行输出,并且包含空格的字符串将自动被引用(在您的情况下,.trim
也删除调用):
~$ env startMarker="88000" stopMarker="89000" \
raku -MText::CSV -e 'my $start = %*ENV<startMarker>; my $stop = %*ENV<stopMarker>; \
my @rows; my $csv = Text::CSV.new( sep => ","); \
while ($csv.getline($*IN)) -> $row { @rows.push: $row if $start < $row.[5] < $stop; }; \
csv(in => @rows, out => $*OUT);' < ~/raphui_771255.csv
mcu_i,INIT,200,iFlash," 11593925"," 88347",,0x00092684,r,0x4606b570," ok",," 32",single,op-c,0,," 0"," 0"," 0",
mcu_i,INIT,200,iFlash," 11593931"," 88348",,0x00092678,r,0x28003801," ok",," 32",single,op-c,0,," 0"," 0"," 0",
mcu_i,INIT,200,iFlash," 10593931"," 88348",,0x00092678,r,0x28003801," ok",," 32",single,op-c,0,," 0"," 0"," 0",
https://raku.land/zef:Tux/Text::CSV
https://github.com/Tux/CSV/blob/master/doc/Text-CSV.md
https://docs.raku.org
https://raku.org