次の形式の巨大なCSVファイルがあります。
aaa1, "aaa2, aa214", aa21, "aa, a14", aa211, aa44, aaa445
data, data, data, data, data, data, data,
........................................
........................................
ヘッダーに特定の文字列(たとえば)を含む列を抽出したいと思いますa2
。上記の例では、列aaa2
などが含まれていますaa21
。
私が試したawkコマンドは次のとおりです。
awk --csv 'NR==1 {for (i=1; i<=NF; i++) if ($i ~ /a2/) print $i}' file.csv
ただし、これは一致するヘッダーのみを返し、その下の列は返しません。正しい方向を教えてください。私はLinuxシステムを使用しています。
答え1
mlr
この偽のCSV形式は実際にサポートされており、正規表現に基づいてフィールドを切り取ることができます。
$ mlr --csv --csv-trim-leading-space --allow-ragged-csv-input cut -rf a2 your-file.csv
"aaa2, aa214",aa21,aa211
data,data,data
ただし、これはメモリに収まらないCSVには拡張されません。--allow-ragged-csv-input
例と行ごとのフィールド数が異なるCSVを処理するには、いずれの場合でもファイル全体を読み取って列数を把握する必要があります(ヘッダーのない列には数値ヘッダーが自動的に割り当てられます)。
答え2
GNU awkを使用しFPAT
、フィールドに改行文字が含まれていないとします。
awk -v FPAT='[^,]*|\\s*("([^"]|"")*")\\s*' -v OFS=',' '
NR==1 {
for ( inFldNr=1; inFldNr<=NF; inFldNr++ ) {
if ( $inFldNr ~ /a2/ ) {
out2in[++numOutFlds] = inFldNr
}
}
}
{
for ( outFldNr=1; outFldNr<=numOutFlds; outFldNr++ ) {
inFldNr = out2in[outFldNr]
printf "%s%s", $inFldNr, (outFldNr<numOutFlds ? OFS : ORS)
}
}
' file.csv
"aaa2, aa214", aa21, aa211
data, data, data
--csv
入力ファイルが有効なCSVではないため(GNU awkも必要です)、使用しませんでした(,
sと最初の"
sの間にスペースがあり、,
2行目の末尾に続くヘッダーよりも多くのデータ列があります)。 CSVパーサーはこれを処理すると予想してはいけません。また、この問題を解決しても、--csv
各列ヘッダーの周りの引用符が削除されるため、これを維持したいようです。すべてのフィールドが二重引用符で囲まれていない場合は、少し問題があります。フィールドに改行文字を含めることができ、とにかくフィールドの周りの引用符を削除する場合は、--csv
正しい設定を使用するよりも使用する方がはるかに優れています。FPAT
本当に試してみたい場合は、--csv
次の(テストされていません)が役に立ちます。
awk --csv -v OFS=',' '
NR==1 {
for ( inFldNr=1; inFldNr<=NF; inFldNr++ ) {
if ( $inFldNr ~ /a2/ ) {
out2in[++numOutFlds] = inFldNr
}
}
}
{
for ( outFldNr=1; outFldNr<=numOutFlds; outFldNr++ ) {
inFldNr = out2in[outFldNr]
outVal = $inFldNr
if ( outVal ~ ("[" OFS ORS "\"]") ) {
gsub(/"/,"\"\"",outVal)
outVal = "\"" outVal "\""
}
printf "%s%s", outVal, (outFldNr<numOutFlds ? OFS : ORS)
}
}
' file.csv
"
ただし、ループに s を追加する場合、先頭/末尾の空白 (存在する場合) が最初に引用符の内側または外側にあったかどうかを知る簡単な方法がないため、フィールド全体を引用しました。
バラよりawkを使用してcsvを効率的に解析する最も強力な方法は何ですかCSVを解析するためにawkを使用する方法に関する追加情報。
答え3
使用幸せ(以前のPerl_6)
...RakuのText::CSV
モジュールを使用して:
~$ raku -MText::CSV -e 'csv(in => csv(in => $*IN, sep => ", "), out => $*OUT);' < file
上記のコマンドはCSVファイル(すべての列)をメモリに読み込みます。ファイルは std-in で受信され、すべての列は$*IN
std-out で出力されます。$*OUT
カスタム", "
フィールド区切り記号を参照してください。
特定の列をフィルタリングするには(他のすべての列を削除する)、見つかった列の数値インデックスを返すRakuのgrep
キーパラメータを使用します。:k
~$ raku -MText::CSV -e 'my @aoa = csv(in => $*IN, sep => ", ");
my @col-nbrs = @aoa[0].grep(/a2/, :k);
for @aoa.map( *.[@col-nbrs]) {
.map(q["] ~ * ~ q["]).join("\t").put
};' < file
入力例:
aaa1_a, "aaa2, aa214_b", aa21_c, "aa, a14_d", aa211_e, aa44_f, aaa445_g
data1_a, data1_b, data1_c, data1_d, data1_e, data1_f, data1_g,
data2_a, data2_b, data2_c, data2_d, data2_e, data2_f, data2_g,
出力例(上記TSV):
"aaa2, aa214_b" "aa21_c" "aa211_e"
"data1_b" "data1_c" "data1_e"
"data2_b" "data2_c" "data2_e"
上記には、「a2」に一致するすべての列名が含まれています。出力を読みやすくするために、各フィールドのテキストを引用符で囲み、タブjoin
で編集します\t
。引用符がないと、コードははるかに簡単になります。次の列が接続されて\t\t
出力されます。
~$ raku -MText::CSV -e 'my @aoa = csv(in => $*IN, sep => ", ");
my @col-nbrs = @aoa[0].grep(/a2/, :k);
for @aoa.map( *.[@col-nbrs]) {
$_.join("\t\t").put
};' < file
aaa2, aa214_b aa21_c aa211_e
data1_b data1_c data1_e
data2_b data2_c data2_e
最後に、デフォルトでは、1.列のカンマ、2.スペースを含む二重引用符フィールドなどのText::CSV
出力機能を利用できます。join
,
% raku -MText::CSV -e 'my @aoa = csv(in => $*IN, sep => ", ");
my @col-nbrs = @aoa[0].grep(/a2/, :k);
my @filtered; for @aoa.map( *.[@col-nbrs] ) {
@filtered.push($_);
}; csv(in => @filtered, out => $*OUT);' < file
"aaa2, aa214_b",aa21_c,aa211_e
data1_b,data1_c,data1_e
data2_b,data2_c,data2_e
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
答え4
もう一つの非常に便利なツールはアヒルデータベース。走る
duckdb --csv -c "SELECT COLUMNS('.*a2.*') from read_csv_auto('input.csv',HEADER = true)" >output.csv
あなたは得る
AAA2、AAA214 | AA21 | AA211 |
---|---|---|
データ | データ | データ |
データ | データ | データ |