Awk: 引用した場合は、列を連結してコンマを削除します。

Awk: 引用した場合は、列を連結してコンマを削除します。

私は次のデータを持っています:

COL1, COL2, COL3
a, b, c
d, "e,f,g", h

私はAwkがそれを次のように扱いたいと思います:

COL1, COL2, COL3
a, b, c
d, efg, h

カンマで区切られた各文字列は、任意の数の文字にすることができます。簡単にするために、ここでは単一の文字のみを使用しました。

これは正しく機能しません。

echo "COL1, COL2, COL3
a, b, c
d, "e,f,g", h" | awk -F ',' '{for (i=1; i<=NF; i++) gsub(/^"|"$|,/,"",$i); print}'

出力:

a, b, c
d  "e f g  h

答え1

csvformat -S(またはcsvformat --skipinitialspace)を使用してください。csvkit各コンマの後の最初の空白文字を削除して、データを適切に引用符付きCSVレコードに変換します。それからミラーmlr)は各レコードの各フィールドを繰り返し、埋め込まれたコンマを削除します。

$ csvformat -S file | mlr --csv put 'for (k,v in $*) { $[k] = gsub(v,",","") }'
COL1,COL2,COL3
a,b,c
d,efg,h

どちらのツールもCSVをサポートし、引用符フィールド、カンマ挿入、改行挿入などを含むCSVレコードを読み取る方法を知っています。フィールドに引用符が必要な場合、csvkitツールとMillerは両方とも引用符付きフィールドを出力します。

たとえば、折り返しフィールドと引用符付きの他のフィールドを含むデータにレコードを追加します。

$ cat file
COL1, COL2, COL3
a, b, c
d, "e,f,g", h
My data, "Line 1,
Line 2", "This is a quote: ""The, quote"""
$ csvformat -S file | mlr --csv put 'for (k,v in $*) { $[k] = gsub(v,",","") }'
COL1,COL2,COL3
a,b,c
d,efg,h
My data,"Line 1
Line 2","This is a quote: ""The quote"""

答え2

ある awk の場合、入力が引用符の外側のすべてのコンマの後にスペースを表示し、引用符付きフィールド内に二重引用符や改行文字がなく、引用符付きフィールド内にカンマの後にスペースがないように見える場合:

$ awk 'BEGIN{FS=OFS=", "} {for (i=1; i<=NF; i++) gsub(/[",]/,"",$i)} 1' file
COL1, COL2, COL3
a, b, c
d, efg, h

あるいは、FPAT入力の各フィールドに先行スペースがあり、引用符付きフィールド内に二重引用符や改行がなく、引用符付きフィールド内にカンマの後にスペースがある場合は、GNU awkを使用できます。

$ awk -v FPAT='([^,]*)|( *"[^"]+")' -v OFS=',' '
    { for (i=1; i<=NF; i++) gsub(/[",]/,"",$i) }
1' file
COL1, COL2, COL3
a, b, c
d, efg, h

バラよりawkを使用してcsvを効率的に解析する最も強力な方法は何ですかCSVを解析するためにawkを使用する方法に関する追加情報。

答え3

今、適切な解決策を見つけたようです。

'{ for (i=1; i<=NF; i+=1)
    { gsub(/^"|",*$|,/,"",$i);
      printf $i ((i != NF) ? ", " : "\n")
    }
 }'

...しかし、フィールドにスペースが含まれている場合は機能しません。これは働きます:

# delimit by comma
-F"," '{
    # m non-zero will tell us if we are in quoted section
    m=0;
    # iterate over every field
    for (i=1; i<=NF; i+=1) {
        # we found a field that starts with possible white-space
        # followed by a quote
        if (match($i,"^ *\"")) {
            # if we are not already in a quoted section, remove the quote, and set 'm'
            if (!m) {sub(/^ *\"/,"",$i)}; m++ }
            # if we are in a quoted section and we encounter a 
            # quote, set 'm' to next lowest-level of quoting
            else if (match($i, "\"")) {m--; 
                # and if we are now outside of the quoted field, remove the quote
                if (!m) {sub("\"","",$i)}};
            # print a comma delimeter unless we're at the last field,
            # in which case we put in a newline
            printf ($i (i==NF? "\n" : (m?"":", ")))
        }
    }
}'

よりコンパクトなソリューションを知りたいです!

答え4

これは少しコンパクトで、他のアプローチをとります。提供されたテストデータを正しく処理します。

BEGIN { FS="\"" }

{
    separator = ""
    for (i = 1; i <= NF; i++) {
        if (i % 2) {
            # Odd numbered field, handle as CSV
            n = split($i, parts, ",")
            for (j = 1; j <= n; j++) {
                printf "%s%s", separator, parts[j];
                separator = ","
            }
        }
        else {
            # Even numbered field, handle as quoted text
            gsub(",", "", $i)
            printf "%s", $i;
            separator = ""
        }
    }
    print "";
}

以下を使用してテストしました。

COL1, COL2, COL3
a, b, c
d, "e,f,g" , h
"i,j,k"
"l,m",n,o
p,"q"
s, t,u, "w,,z"

上記のコードは二重引用符をデフォルトの区切り文字として扱います。引用符がペアになっているとします。この場合、偶数フィールド($2、$4、$6、...)は引用符で囲まれ、奇数フィールド($1、$3、$5、...)は外側の引用符です。各フィールドタイプ(引用符がある場合、または奇数として引用されていない場合を含む)は異なる方法で処理されます。

必要に応じて、正規表現をフィールド区切り記号(FS)として使用してエスケープ引用符を処理できます。すべてのスペースを削除したいのか、それとも追加できるのかわかりません。

関連情報