특정 csv 열에서 2개의 단어를 grep하고 개수를 계산합니다.

특정 csv 열에서 2개의 단어를 grep하고 개수를 계산합니다.

다음 grep/awk 쿼리를 완료하는 더 나은 방법을 찾으려고 노력 중입니다. 다음은 문제의 간단한 예입니다.

나는 정규식을 사용하여 이것을 달성했습니다.

grep -Po ^(?:[^,]+,\s?){7}(Want|Need) | awk -F ',' 'NR>=2{print $8}' | sort | uniq -c

내 CSV 파일은 다음과 같습니다.

1896,Ranger,2021,State,Postcode,Surname,Industry,Want,Turbo,Good
1896,Ranger,2021,State,Postcode,Surname,Industry,Selling,Turbo,Good
1896,Ranger,2021,State,Postcode,Surname,Industry,Need,Turbo,Good

위 작업은 grep을 사용하여 전체 줄을 인쇄합니다.

1896,Ranger,2021,State,Postcode,Surname,Industry,Want
1896,Ranger,2021,State,Postcode,Surname,Industry,Need

그런 다음 열 8의 값을 계산할 수 있습니다. 내 질문은 정규식을 사용하여 선택한 그룹만 반환하도록 grep/regex 쿼리를 작성하는 방법입니다.

예를 들어:

Want
Need

이 글을 쓴 이유는 순전히 여기에서 정규식을 사용하는 더 나은 방법을 이해하기 위해서입니다. 나는 이것을 수행하는 다른 방법이 있다는 것을 알고 있습니다.

답변1

PCRE 어설션을 찾고 있는 것 같습니다 \K. ~에서페레:

\K(Perl 5.10.0부터 사용 가능)라는 특별한 형태의 이 구성이 있는데, 이는 정규식 엔진이 $&로 묶지 않고 \K 이전에 일치하는 항목을 "유지"하도록 합니다.

그래서

$ grep -Po '^(?:[^,]+,\s?){7}\K(Want|Need)' file.csv
Want
Need

보다 일반적으로 이런 종류의 작업은 다음을 사용하여 수행됩니다.뒤를 봐주장 - 그러나 Perl은 가변 길이 뒤돌아보기를 지원하지 않으며 grep -P도 지원하지 않습니다.

$ grep -Po '^(?<=(?:[^,]+,\s?){7})(Want|Need)' file.csv
grep: lookbehind assertion is not fixed length

당신은 또한 볼 수 있습니다앞으로 및 뒤로 길이가 0인 어설션

답변2

이것은 일치를 수행하기 위해 libpcre(perl 정규 표현식의 독립 실행형 구현)를 사용하는 -PGNU 구현의 비표준(선택적이며 오랫동안 실험적으로 간주된) 옵션입니다.grep

libpcre이제 완전한 구현으로 발전하여 일부 GNU/Linux 배포판의 자체 패키지에서 찾을 수 있지만 grep예제 코드( )로 자체 명령이 제공됩니다 .pcregrepgrep

pcregrepgrep해당 캡처 그룹을 출력하기 위해 선택적 숫자 인수를 사용하도록 GNU 의 -o비표준 옵션을 확장했습니다 .

그래서 여기 있습니다:

pcregrep -o1 '^(?:[^,]+,\s?){7}(Want|Need)'

또는 GNU가 없는 시스템 grep(또는 grepPCRE 지원 없이 GNU를 구축한 시스템) 에서도 작동할 수 있다는 장점이 있는 실제 시스템을 사용할 수도 있습니다 pcregrep.

perl -lne 'print $1 if /^(?:[^,]+,\s?){7}(Want|Need)/'

그러나 perl기본적으로 입력은 GNU에서와 같이 로케일의 텍스트 인코딩에 따라 디코딩되지 않습니다 grep. 이 특별한 경우, 일치하는 텍스트는 이식 가능한 문자 집합의 문자만 사용합니다. 이는 입력이 로케일과 다른 인코딩으로 되어 있어도 여전히 작동하므로 매우 유리할 수 있습니다.

perl입력의 텍스트를 로캘 인코딩에 따라 디코딩(및 출력에서 ​​인코딩) 하려면 를 추가하면 됩니다 -Mopen=locale.


그러나 귀하의 경우에는 Perl 정규 표현식을 사용할 가치가 없습니다. 여기에서 사용하는 모든 Perl 연산자에는 표준 ERE 연산자와 동등한 것이 있습니다(대체를 제외한 BRE도 마찬가지).

  • (?:...): 단지 perl/ERE (...)또는 BRE 이며 \(...\)캡처가 없습니다.
  • +: ERE에서도 동일, \{1,\}BRE에서도 동일
  • ?: ERE와 동일, \{0,1\}ERE에서는
  • {7}: ERE에서도 동일, \{7\}BRE에서도 동일
  • (Want|Need): ERE와 동일합니다(교대 방향을 선택할 때 동작이 약간 다르지만).
  • \s: [[:space:]]BRE와 ERE에서
  • ^, [^,]: BRE나 ERE에서 동일

sed는 패턴의 일치하는 부분을 추출하는 도구입니다(반면 , grepafter ed명령은 정규식과 일치하는 행을 g/re/p인쇄합니다 ). p표준은 BRE를 사용하지만 대부분의 구현에서는 ERE로의 전환을 지원합니다(이는 표준의 다음 버전에 추가될 예정입니다).resedsed-E

따라서 여기에서는 perl위 명령과 동일하게 이식 가능한 작업도 수행할 수 있습니다.

LC_ALL=C sed -nE 's/^([^,]+,[[:space:]]?){7}(Want|Need).*$/\2/p'

아니면 -E:

LC_ALL=C sed -n 's/^\([^,]\{1,\},[[:space:]]\{0,1\}\)\{7\}\(Want\).*$/\2/p; t
                 s/^\([^,]\{1,\},[[:space:]]\{0,1\}\)\{7\}\(Need\).*$/\2/p'

아니면 다른 것으로 바꾸세요 Want:Need

LC_ALL=C sed -E 's/^(([^,]+,[[:space:]]?){7})(Want|Need)/\1Desire/'
LC_ALL=C sed 's/^\(\([^,]\{1,\},[[:space:]]\{0,1\}\)\{7\}\)Want/\1Desire/; t
              s/^\(\([^,]\{1,\},[[:space:]]\{0,1\}\)\{7\}\)Need/\1Desire/

1 그 이후로 다른 구현에서는 ast-open처럼 항상 libpcre를 사용하는 것이 아니라 유사한 정규식을 -P사용하기 위한 자체 옵션을 추가했습니다 (탐색 주장은 지원하지만 지원하지 않음 ).perlgrep\K

답변3

이미 awk를 사용하고 있으므로 여기서는 필요하지 않습니다 grep. 당신은 그것을 필요로하지도 않고 sort필요 uniq -c하지도 않습니다. 예를 들어:

$ awk -v search=Want -F, '$8 ~ search { count[$8]++ };
    END { for (f in count) { printf "%5i\t%s\n", count[f], f}}' input.csv 
    1   Want

$ awk -v search='Want|Need' -F, '$8 ~ search { count[$8]++ };
    END { for (f in count) { printf "%5i\t%s\n", count[f], f}}' input.csv 
    1   Want
    1   Need

또는 일치하는 줄도 인쇄하려면 다음을 수행하십시오.

$ awk -v search='Want|Need' -F, '$8 ~ search { count[$8]++ ; print };
    END { for (f in count) { printf "%5i\t%s\n", count[f], f}}' input.csv 
1896,Ranger,2021,State,Postcode,Surname,Industry,Want,Turbo,Good
1896,Ranger,2021,State,Postcode,Surname,Industry,Need,Turbo,Good
    1   Want
    1   Need

-v IGNORECASE=1명령줄에 추가하여 GNU awk에 대소문자 구분을 추가하거나 원하는 경우 정확한 일치와 같은 고급 기능을 추가할 수도 있습니다.

$ awk -v search='want' -v exact=1 -v IGNORECASE=1 -F, '
    BEGIN {if (exact == 1) search = "^(" search ")$"};
    $8 ~ search { count[$8]++ ; print };
    END { for (f in count) { printf "%5i\t%s\n", count[f], f}}' input.csv 
1896,Ranger,2021,State,Postcode,Surname,Industry,Want,Turbo,Good
    1   Want

다음은 ant에 있는 동안 Want전체 필드 8과 정확히 일치하지 않기 때문에 출력을 생성하지 않습니다.

$ awk -v search='ant' -v exact=1 -v IGNORECASE=1 -F, '
    BEGIN {if (exact == 1) search = "^(" search ")$"};
    $8 ~ search { count[$8]++ ; print };
    END { for (f in count) { printf "%5i\t%s\n", count[f], f}}' input.csv 

참고: 명령줄 옵션 처리를 수행하는 더 좋은 방법이 분명히 있습니다(예:선택 항목 가져오기함수 또는 쉘 스크립트 래퍼를 작성하여 sh/bash 내장 기능을 사용 getopt하지만 -vawk의 옵션을 사용하여 스크립트 외부에서 awk에 변수를 설정하는 것은 이와 같은 간단한 작업에 간단하고 편리합니다.

그런데 awk는 변수를 스크립트 자체 뒤의 명령줄에 추가하여 사용하지 않고 변수를 할당할 수도 있습니다 -v(awk는 형식의 모든 인수를 x=y변수 x를 값 y로 설정하는 것으로 해석합니다. 불행하게도 이로 인해 사용하기가 어렵습니다. 그들 안에 =– 아마도 불가능할 수도 있습니다. "그럼 하지 마세요" 이외의 해결책을 찾았는지 기억이 나지 않습니다).

그러나 를 사용할 때와는 달리 -v이러한 변수는아니요이는 BEGIN {}성명서에서 확인할 수 있다. 예를 들어, ant다음을 설정하더라도 다음은 일치합니다 exact=1.

$ awk -F, 'BEGIN {if (exact == 1) search = "^(" search ")$"};
           $8 ~ search { count[$8]++ ; print };
           END { for (f in count) { printf "%5i\t%s\n", count[f], f}}' \
    search=ant IGNORECASE=1 exact=1 input.csv 
1896,Ranger,2021,State,Postcode,Surname,Industry,Want,Turbo,Good
    1   Want

GNU awk 매뉴얼 페이지에서:

명령줄의 파일 이름이 다음 형식인 경우 var=val 변수 할당으로 처리됩니다. 변수에 var 값이 할당됩니다 val. (가끔 이런 경우가 있어요.뒤쪽에BEGIN실행된 규칙이 없습니다. )

명령줄 변수 할당은 AWK가 입력이 필드와 레코드로 분류되는 방식을 제어하는 ​​데 사용하는 변수에 값을 동적으로 할당하는 데 가장 유용합니다. 단일 데이터 파일에 여러 번 전달해야 하는 경우 상태를 제어하는 ​​데에도 유용합니다.

IMO에서는 이를 이전 awk 스크립트와 호환되는 레거시 기능으로 처리하고 -v.

-v var=val
--assign var=val

프로그램이 실행되기 전에 변수에 값이 할당됩니다 val. var이러한 변수 값BEGINAWK 프로그램 에 사용 가능한 규칙입니다.

(위 인용문 안의 "이후"와 "이다"는 제가 굵은 글씨로 강조한 것입니다)

답변4

0검색 중인 문자열 중 하나가 입력에 나타나지 않는 경우 전혀 인쇄하지 않고 개수를 인쇄하는 것을 보고 싶다면 강력하고 휴대 가능하며 효율적이고 간결한 방법은 다음과 같습니다. 이것:

$ awk -F',' -v tgts='Want,Need' '
    { cnt[$8]++ }
    END { split(tgts,t); for (i in t) print t[i], cnt[t[i]]+0 }
' file
Want 1
Need 1

따라서 여기서 정규식이 어디에 적용되는지 파악하기가 어렵습니다. 어쩌면 다음과 같을 수도 있습니다:

$ awk -F',' -v tgts='Want|Need' '
    $8 ~ ("^"tgts"$") { cnt[$8]++ }
    END { split(tgts,t,/[|]/); for (i in t) print t[i], cnt[t[i]]+0 }
' file
Want 1
Need 1

또는:

$ awk -F',' -v tgts='Want|Need' '
    $0 ~ ("([^,]*,){7}"tgts"(,|$)") { cnt[$8]++ }
    END { split(tgts,t,/[|]/); for (i in t) print t[i], cnt[t[i]]+0 }
' file
Want 1
Need 1

그러나 정규식은 스크립트를 복잡하게 만들고 더 취약하게 만들 뿐이며(찾으려는 문자열에 .또는 같은 정규식 메타 문자가 포함되어 있으면 정규식이 있는 스크립트는 실패 *하지만 첫 번째 스크립트는 계속 작동합니다), 다음이 없으면 아무 값도 추가하지 않습니다. $8귀하의 입력에는 수십억 개의 고유 값이 있습니다.

관련 정보