명령줄에서 csv 파일을 구문 분석하는 데 문제가 있습니다.

명령줄에서 csv 파일을 구문 분석하는 데 문제가 있습니다.

나는 하루 중 가장 많은 시간을 들여 작업한 CSV 파일을 가지고 있지만 awk의 정규식을 사용하여 올바르게 구문 분석할 수 있는 행운이 없습니다.

awk는 예상대로 정규식을 처리하지 않습니다.

입력은 다음과 같습니다.

  • GNU Awk 4.1.4, API: 1.1(GNU MPFR 3.1.5-p2, GNU MP 6.1.2)
  • 정규식:/(\[(.*?)\])|[^,]+/g
  • 샘플 텍스트hostname,hostname.domain.com,hostname.domain.com,windows,6.2.9200,1.2.3,location,environment,[role1, role2, role3],[recipe1, recipe2, recipe3],2019-01-10 06:06:31
  • 원본 텍스트(이 질문에 명시적으로 나열되지 않은 단계에서 큰따옴표를 제거하기 전): hostname,hostname.domain.com,hostname.domain.com,windows,6.2.9200,1.2.3,location,environment,"[""role1"", ""role2"", ""role3""]","[""recipe1"", ""recipe2"", ""recipe3""]",2019-01-10 06:06:31

내가 이것을 실행할 때정규식 웹사이트, 이는 올바른 일치를 보여줍니다. Regexr의 스크린샷

cat -> sed -> awk(위의 샘플 텍스트는 sed에 있음)에서 파이프하고 다음 명령을 실행했습니다([]에 포함된 첫 번째 필드의 전체 내용을 포함하는 처음 9개 필드만 원하지만 아무것도 원하지 않습니다). 이후):

awk '/(\[(.*?)\])|[^,]+/g{print $1,$2,$3,$4,$5,$6,$7,$8,$9}'

내가 기대하는 결과는 다음과 같습니다. hostname,hostname.domain.com,hostname.domain.com,windows,6.2.9200,1.2.3,location,environment,[role1, role2, role3]

노트:이에 대한 중요한 부분은 역할(대괄호 사이)이 있는 필드를 단일 필드로 처리하거나 최소한 출력에 모든 역할을 포함하되 레시피는 포함하지 않는 것입니다.

내가 실제로 얻는 것은 입력의 전체 라인입니다.

변수를 사용하여 awk에서 다음 필드 할당을 찾았습니다.

  • 1달러 =hostname,hostname.domain.com,hostname.domain.com,windows,6.2.9200,1.2.3,location,environment,[role1,
  • $2 =role2,
  • 3달러 =role3],[recipe1,
  • 4달러 =recipe2,
  • $5=recipe3],2019-01-10
  • 6달러 =06:06:31

허용되는 답변을 사용해 보았습니다.이 스택 오버플로 질문직접적으로 "" 대신 []를 구분 기호로 사용하도록 조정해 보았지만 여전히 역할 필드를 단일 필드로 처리하지 않습니다.

답변1

복잡한 CSV 파일, 특히 필드에 따옴표 구분 기호(이 경우 쉼표)가 포함될 수 있는 파일을 처리하는 경우 올바른 CSV 파서를 사용하면 많은 문제를 줄일 수 있습니다.csvtool

$ echo 'hostname,hostname.domain.com,hostname.domain.com,windows,6.2.9200,1.2.3,location,environment,"[""role1"", ""role2"", ""role3""]","[""recipe1"", ""recipe2"", ""recipe3""]",2019-01-10 06:06:31' | 
    csvtool col 1-9 -
hostname,hostname.domain.com,hostname.domain.com,windows,6.2.9200,1.2.3,location,environment,"[""role1"", ""role2"", ""role3""]"

또는 (따옴표 제거)

$ echo 'hostname,hostname.domain.com,hostname.domain.com,windows,6.2.9200,1.2.3,location,environment,"[""role1"", ""role2"", ""role3""]","[""recipe1"", ""recipe2"", ""recipe3""]",2019-01-10 06:06:31' | 
    csvtool col 1-9 - | tr -d '"'
hostname,hostname.domain.com,hostname.domain.com,windows,6.2.9200,1.2.3,location,environment,[role1, role2, role3]

예를 들어 독립형 CSV 파서를 얻을 수 없는 경우 csvtoolPerl과 Python 모두에 대한 CSV 모듈이 있습니다.

perl -MText::CSV -lpe '
  BEGIN{$p = Text::CSV->new()} 
  $_ = join ",", map { $_ = s/"//gr } ($p->fields())[0..8] if $p->parse($_)
'

답변2

기본적으로 awk필드는 공백으로 정의되며, 이는 표시되는 출력을 얻는 이유를 설명합니다. 쉼표를 사용하여 필드를 구분하려면 다음을 사용해야 합니다 -F.

awk -F, '{...}' 

쉼표로 구분된 출력을 인쇄 하려면 변수를 awk설정해야 합니다 .OFS

awk -F, -vOFS=, '{...}' 

[role1, role2, role3]여기서 진짜 어려운 점은 이를 단일 필드 로 처리하려고 하는데 3개의 필드가 있다는 것입니다. 거기에 쉼표가 있어서 [role1, role2, 로 나뉘게 됩니다 role3]. 항상 3개의 필드가 있다는 것을 알고 있다면 간단합니다.

$ awk -F, -vOFS=, '{print $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11}' file
hostname,hostname.domain.com,hostname.domain.com,windows,6.2.9200,1.2.3,location,environment,[role1, role2, role3]

하지만 지금 추가한 원시 데이터를 기반으로올바른 CSV 파서항상 더 나은 접근 방식이지만 awk원래 입력 데이터에서 실행하면 됩니다.

$ awk -F']' -vOFS=, '{gsub(/"/,"");print $1"]"}' file
hostname,hostname.domain.com,hostname.domain.com,windows,6.2.9200,1.2.3,location,environment,[role1, role2, role3]

]비결은 이를 필드 구분 기호 로 사용 하고 awk첫 번째 필드만 인쇄되도록 지정하는 것입니다. 그러면 첫 번째 항목까지 모든 내용이 인쇄됩니다 ]. 그런 다음 다시 추가합니다 ](필드를 만들 때 제거되었기 때문입니다). gsub모든 따옴표를 제거하십시오 .

관련 정보