私は次のデータを持っています:
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)として使用してエスケープ引用符を処理できます。すべてのスペースを削除したいのか、それとも追加できるのかわかりません。