
필드에 줄 바꿈(큰따옴표로 포함)이 있는 파일이 있는 경우 NUL을 레코드 구분 기호로 사용한 다음 원하는 레코드를 선택하려고 했습니다. 이를 위해 줄 끝을 NUL로 바꾼 다음 개행 문자로 분할된 필드를 수정했습니다( 를 사용하여 수행 sed
). 그러나 (GNU)의 첫 번째 필드 awk
와 문자열을 정확하게 일치시키는 것은 실패합니다. 흥미롭게도 첫 번째 필드에서 문자열 패턴 일치가 실패하므로 이것이 RS="\x00"
올바르게 적용되었다고 가정하게 됩니다.
왜 실패할까요? 패턴 일치가 작동하는 이유는 무엇입니까?
예시 파일 input.txt
:
head1,head2,head3
a,b,c
b,no a in first field,c
a,"with quotes",c
a,"with ,",c
b,a,1
a,"with
newline",c
b,1,a
awk
NUL 작업을 도입하기 전에 정확한 문자열을 통해 선택 항목을 기록하세요 .
$awk 'BEGIN {FS=OFS=","} {if ($1=="a") print}' input.txt
결과:
a,b,c
a,"with quotes",c
a,"with ,",c
a,"with
NUL을 도입하고 "newline-splits" 작업 수정( "with\n newline"
항목 참고):
$sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt | cat -A
head1,head2,head3^@$
a,b,c^@$
b,no a in first field,c^@$
a,"with quotes",c^@$
a,"with ,",c^@$
b,a,1^@$
a,"with$
newline",c^@$
b,1,a^@$
필드 1의 패턴 일치를 사용하면 작동합니다( "a"
다른 필드에서는 실패하지만 "head1"
일치하는 방법에 유의하세요).
$sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt |
awk 'BEGIN {RS=ORS="\x00" ; FS=OFS=","}
{ if ($1~"a") print}' |
cat -A
head1,head2,head3^@$
a,b,c^@$
a,"with quotes",c^@$
a,"with ,",c^@$
a,"with$
newline",c^@
하지만"a"
: 필드 1 의 정확한 일치가 실패합니다.
sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt |
awk 'BEGIN {RS=ORS="\x00" ; FS=OFS=","} { if ($1=="a") print}'
##<no output>##
내가 어디 잘못 됐나요? NUL을 사용하기 전에 작업이 왜 됩니까 RS
?
답변1
sed 명령은 개행 문자( \n
)를 NUL( \0
)로 변경하는 것이 아니라 NUL + 개행 문자( \0\n
)로 변경합니다( cat -A
표시됨).
RS가 로 설정된 GNU awk를 사용하면 \0
후속 레코드(및 첫 번째 필드)의 첫 번째 문자가 가 되어 \n
정확한 일치가 중단됩니다.
그리고 's/\(,"[^,"]*\)\x00/\1/'
개행 분할 수정은 이를 전혀 변경하지 않으며 단지 newline",c
이전 레코드에 레코드를 추가할 뿐입니다.
빠르고 더러운 "솔루션 " RS
은 \0\n
. \0
그러나 awk로 구문 분석할 수 있도록 csv 파일을 마사지하는 방식은 신뢰할 수 없으므로 실제로 더 나은 것을 찾아야 합니다.
마지막 예를 들면 다음과 같습니다.
sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt |
gawk 'BEGIN {RS=ORS="\x00\n" ; FS=OFS=","} { if ($1=="a") print}' | cat -A
a,b,c^@$
a,"with quotes",c^@$
a,"with ,",c^@$
a,"with$
newline",c^@$
sed -e 's/$/\x00/' -e 's/\(,"[^,"]*\)\x00/\1/' input.txt |
gawk 'BEGIN {RS="\x00\n" ; FS=OFS=","} { if ($1=="a") print}'
a,b,c
a,"with quotes",c
a,"with ,",c
a,"with
newline",c
답변2
파일에 CRLF 줄 끝이 있는 LF 중간 필드가 포함될 수 있습니다(예: MS-Excel에서 내보낸 경우). 이 경우 gawk에 필요한 것은 다음과 같습니다.
awk 'BEGIN{RS=ORS="\r\n"; FPAT="[^,]*|(\"[^\"]*\")+"} $1=="a"' file
예를 들어( cat -v
CR을 s로 표시하기 위해 사용 ^M
):
$ cat -v file
head1,head2,head3^M
a,b,c^M
b,no a in first field,c^M
a,"with quotes",c^M
a,"with ,",c^M
b,a,1^M
a,"with
newline",c^M
b,1,a^M
$ awk 'BEGIN{RS=ORS="\r\n"; FPAT="[^,]*|(\"[^\"]*\")+"} $1=="a"' file | cat -v
a,b,c^M
a,"with quotes",c^M
a,"with ,",c^M
a,"with
newline",c^M
위의 방법이 효과가 없는 이유가 있다면 다음을 참조하세요.https://stackoverflow.com/questions/45420535/whats-the-most-robust-way-to-efficiently-parse-csv-using-awk또는 gawkextlib에서 gawks CSV 파서 확장을 다운로드/사용하세요.
답변3
혼합 sed awk 접근 방식:
$ < file \
sed -e '
s/$/\x00/
s/\(,"[^,"]*\)\x00/\1/
H;1h;$!d;g
s/\x00\n/\x00/g
' |
awk '/^a,/' RS="\x00" -
설명: sed+awk 혼합을 사용하여 원하는 결과를 얻기 위해 코드를 약간 수정했습니다. 주요 아이디어는 sed가 항상 넣는 개행 문자를 제거하는 것입니다. 그래서 우리는 모든 레코드를 처리한 후 sed가 인쇄되는 것을 보류합니다. 그런 다음 eof에서 줄 바꿈을 제거하고 NUL로 구분된 이 데이터를 레코드 구분 기호로 NUL을 사용하여 awk에 전달합니다. 그런 다음 a로 시작하는 레코드를 찾습니다.
산출:
a,b,c
a,"with quotes",c
a,"with ,",c
a,"with
newline",c
아래에는 awk 전용 메소드와 sed 전용 메소드가 제공됩니다. 그들은 인용된 필드 내의 인용문을 두 배로 늘리는 데 의존합니다.
순수한 sed 접근 방식:
$ sed -Ee ':a
/^(([^"]*"){2})*[^"]*$/!{
$d;N;ba
}
/^a,/!d
' file
순수한 awk 접근 방식
$ awk -F\" '
!(NF%2){
t = $0; n = NF
while (getline a > 0) {
t = t ORS a
n = n + split(a, _x, FS)
if (!(nf%2)) break
}
$0 = t
}/^a,/
' file