CSVファイルの列を視覚的に並べ替える

CSVファイルの列を視覚的に並べ替える

sedまたはを使用してawkCSVファイルの列を視覚的に並べ替えることはできますか?

たとえば、

たとえば、

a,b,c,some stuff,"some, other, stuff",d,2023-03-10 18:37:00
y,x,z,t,cool,thing,2022-04-12 21:44:00

到着する:

a, b, c, some stuff,"some, other, stuff",     d, 2023-03-10 18:37:00<EOL>
x, y, z,          t,                cool, thing, 2022-04-12 21:44:00<EOL>

コンマを含むテキストを含む二重引用符フィールドがあります。

試してみcolumnましたが、bsdmainutilsそうでした。確かにこのデータは処理できません。

答え1

このタイプのCSVファイル:

a, b, c, some stuff,"some, other, stuff",     d, 2023-03-10 18:37:00<EOL>
x, y, z,          t,                cool, thing, 2022-04-12 21:44:00<EOL>

いいえ本物フィールドを変更しているので、同じデータファイルを使用しなくなりました。構文解析では、上記の幅のために元のコンテンツが解析されます"t"(非標準の区切り文字を解析するために正規表現を使用しない限り)。" t""some stuff",[variable space]

すべてのフィールドに引用符を適用すると、これらの新しいフィールドをより明確に表示するcsvファイルを取得できます。これを行うRubyメソッドは次のとおりです。

ruby -r csv -e '
cols={}
data=CSV.parse($<.read)
data.transpose.each_with_index{|sa,i| 
    cols[i]=sa.max_by{|e| e.length}; cols[i]=cols[i].length 
}
puts CSV.generate(force_quotes:true){|csv|
    data.each{|row|
        csv<<row.map.with_index{|e, i| e.rjust(cols[i] ) }
    }
}
' file

印刷:

"a","b","c","some stuff","some, other, stuff","    d","2023-03-10 18:37:00"
"y","x","z","         t","              cool","thing","2022-04-12 21:44:00"

または、引用符付きフィールドと引用符付きフィールドが本当に必要な場合は、次のようにします。

ruby -r csv -e '
lcl_csv_opt={:row_sep=>nil}
data=CSV.parse($<.read)
cols=data.transpose.map.with_index{|sa,i| 
    x=sa.max_by{|e| [e].to_csv(**lcl_csv_opt).length}
    [i,"#{[x].to_csv(**lcl_csv_opt)}"]
}.to_h
puts CSV.generate(){|csv|
    data.each{|row|
        csv<<row.map.with_index{|e, i| 
            [e].to_csv(**lcl_csv_opt)==cols[i] ? e : e.rjust(cols[i].length ) 
        }
    }
}
' file

印刷:

a,b,c,some stuff,"some, other, stuff",    d,2023-03-10 18:37:00
y,x,z,         t,                cool,thing,2022-04-12 21:44:00

また、フィールドで迷惑なエスケープ引用符を処理します。一方:

$ cat file
a,b,c,some stuff,"some, other, stuff",d,2023-03-10 18:37:00
y,x,z,t,cool,"""thing"", quoted",2022-04-12 21:44:00

2番目のバージョンでは、次のものを印刷します。

a,b,c,some stuff,"some, other, stuff",                  d,2023-03-10 18:37:00
y,x,z,         t,                cool,"""thing"", quoted",2022-04-12 21:44:00

答え2

コンマを含むテキストを含む二重引用符フィールドがあります。

もしそうなら、単純なテキスト解析を忘れてください。複雑なCSVを解析し、きれいに印刷できるものを入手してください。

ミラー選択するツールです。出力形式として「きれいな印刷」を指定できます。

mlr --icsv --opprint cat example.csv

Pythonの組み込みcsvモジュールを使用することもできます。

import csv

rows = []
maxwidths = []
with open("foo.csv") as csvfile:
    reader = csv.reader(csvfile, delimiter=",", quotechar='"')
    for row in reader:
        for column_idx, entry in enumerate(row):
            if column_idx >= len(maxwidths):
                maxwidths += [len(entry)]
            else:
                maxwidths[column_idx] = max(maxwidths[column_idx], len(entry))
        rows += [row]

for row in rows:
    print(", ".join([f"{col:<{width}}" for col, width in zip(row, maxwidths)]))

答え3

FPATGNU awkと2段階の方法を使う:

$ cat tst.awk
BEGIN {
    FPAT = "([^,]*)|(\"([^\"]|\"\")*\")"
    OFS = ", "
}
NR==FNR {
    for ( i=1; i<=NF; i++ ) {
        wid = length($i)
        wids[i] = ( wid > wids[i] ? wid : wids[i] )
    }
    next
}
{
    for ( i=1; i<=NF; i++ ) {
        printf "%*s%s", wids[i], $i, (i<NF ? OFS : ORS)
    }
}

$ awk -f tst.awk file file
a, b, c, some stuff, "some, other, stuff",     d, 2023-03-10 18:37:00
y, x, z,          t,                 cool, thing, 2022-04-12 21:44:00

あるいは、awkと同じアプローチを使用して、ループ呼び出しを使用して各レコードをフィールドに分割し、そのフィールドをmatch()配列FPATに格納するコードを直接作成することもできます。上記のgawkが通常のフィールドの一部としてこれを行うのではなく、分割しました:

$ cat tst.awk
BEGIN {
    FPAT = "([^,]*)|(\"([^\"]|\"\")*\")"
    OFS = ", "
}
{
    nf = 0
    rec = $0
    while ( (rec != "") && match(rec,FPAT) ) {
        flds[++nf] = substr(rec,RSTART,RLENGTH)
        rec = substr(rec,RSTART+RLENGTH+1)
    }
}
NR==FNR {
    for ( i=1; i<=nf; i++ ) {
        wid = length(flds[i])
        wids[i] = ( wid > wids[i] ? wid : wids[i] )
    }
    next
}
{
    for ( i=1; i<=nf; i++ ) {
        printf "%*s%s", wids[i], flds[i], (i<nf ? OFS : ORS)
    }
}

$ awk -f tst.awk file file
a, b, c, some stuff, "some, other, stuff",     d, 2023-03-10 18:37:00
y, x, z,          t,                 cool, thing, 2022-04-12 21:44:00

入力を2回読み取るのではなく、入力全体をメモリに保存し、ENDセクションにすべて印刷することを選択できます。この方法の利点はパイプの入力を処理できることであり、欠点は入力ファイルが大きすぎてメモリに入れることができないことです。これはGNU awkバージョンです:

$ cat tst.awk
BEGIN {
    FPAT = "([^,]*)|(\"([^\"]|\"\")*\")"
    OFS = ", "
}
{
    for ( i=1; i<=NF; i++ ) {
        flds[NR,i] = $i
        wid = length($i)
        wids[i] = ( wid > wids[i] ? wid : wids[i] )
    }
}
END {
    for ( rowNr=1; rowNr<=NR; rowNr++ ) {
        for ( i=1; i<=NF; i++ ) {
            printf "%*s%s", wids[i], flds[rowNr,i], (i<NF ? OFS : ORS)
        }
    }
}

$ awk -f tst.awk file
a, b, c, some stuff, "some, other, stuff",     d, 2023-03-10 18:37:00
y, x, z,          t,                 cool, thing, 2022-04-12 21:44:00

そしてすべてのawkバージョン:

$ cat tst.awk
BEGIN {
    FPAT = "([^,]*)|(\"([^\"]|\"\")*\")"
    OFS = ", "
}
{
    nf = 0
    rec = $0
    while ( (rec != "") && match(rec,FPAT) ) {
        fld = substr(rec,RSTART,RLENGTH)
        flds[NR,++nf] = fld
        wid = length(fld)
        wids[nf] = ( wid > wids[nf] ? wid : wids[nf] )
        rec = substr(rec,RSTART+RLENGTH+1)
    }
}
END {
    for ( rowNr=1; rowNr<=NR; rowNr++ ) {
        for ( i=1; i<=nf; i++ ) {
            printf "%*s%s", wids[i], flds[rowNr,i], (i<nf ? OFS : ORS)
        }
    }
}

$ awk -f tst.awk file
a, b, c, some stuff, "some, other, stuff",     d, 2023-03-10 18:37:00
y, x, z,          t,                 cool, thing, 2022-04-12 21:44:00

関連情報