列値に基づいてCSVファイルから行を削除する

列値に基づいてCSVファイルから行を削除する

次の形式の1,200万行を含む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,

次のロジックを使用して、6番目の列の値に基づいて行を削除したいと思います。 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結果が一部の「in-place」で印刷されても標準出力として印刷されない場合も同様です。

はい

10行のファイルがあります。

$ cat somefile
1
2
3
4
5
6
7
8
9
10

デフォルトでは(ファイルを変更せずにすべての行をそのまま印刷します)、10回のstraceシステムコールが実行されます(ソースファイルの各行に1つずつ)。gawkwrite

$ 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(またはパイプで接続)すると、単一のシステム呼び出しのみが呼び出されるように見えます。これは、すべての行をすぐに印刷せず、バッファがいっぱいになった場合にのみ結果をフラッシュするためです。catstracegawkwrite

$ 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次のように使用する場合のセキュリティリスクinplace

私は自分の仕事でうまくいくと予測不可能な結果を​​もたらす可能性があるという@RomeoNinovのコメントに全く同意しませんが、@OlivierDulacのコメント-i inplaceこれは、一般的な使用がセキュリティの脆弱性と見なされる理由を説明する便利な答えを提供します。この問題を解決する方法安全な方法で実行してください。

答え2

1つの可能な方法は(コマンドを書き換えることによって)次のようになります。

gawk  -F, -v s="$start_marker" -v e="$end_marker" '$6 > e || $6 < s'  myfile.csv >/tmp/newfile

現場でawkの操作は推奨されず、安全上の危険があります。また、スクリプトが正しいと100%確信する前にソースファイルを台無しにすることもできます。

答え3

そうでない場合は、awkPerlを試してみてください。

#!/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;
}

正規表現は、1行から最初の5つのカンマ区切りフィールドをスキップし、スペースを無視して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良い反応を得ており、長い間モジュールの作成者/メンテナンス者がText::CSVRaku(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シェル変数にアクセスするために使用できる特別な関連配列もあります。したがって、次は環境(つまりシェル)からシェルstartMarker変数を取得します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

関連情報