나는 다음과 같은 데이터를 가지고 있습니다:
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)로 사용하여 이스케이프 따옴표를 처리할 수 있습니다. 모든 공백을 제거하고 싶은지, 아니면 추가할 수 있는지 잘 모르겠습니다.