根据列值从 csv 文件中删除行

根据列值从 csv 文件中删除行

我有一个包含 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

相关内容