awk: NUL을 레코드 구분 기호로 사용하지 않는 필드의 정확한 문자열 일치

awk: NUL을 레코드 구분 기호로 사용하지 않는 필드의 정확한 문자열 일치

필드에 줄 바꿈(큰따옴표로 포함)이 있는 파일이 있는 경우 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

awkNUL 작업을 도입하기 전에 정확한 문자열을 통해 선택 항목을 기록하세요 .

$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 -vCR을 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

관련 정보