헤더에 특정 하위 문자열이 포함된 csv 파일에서 열을 추출하는 awk 명령

헤더에 특정 하위 문자열이 포함된 csv 파일에서 열을 추출하는 awk 명령

다음과 같은 형식의 거대한 CSV 파일이 있습니다.

aaa1, "aaa2, aa214", aa21, "aa, a14", aa211, aa44, aaa445 
data, data, data, data, data, data, data,
........................................
........................................

헤더에 특정 문자열(예: )이 포함된 열을 추출하고 싶습니다 a2. 위의 예에서는 열 aaa2등이 포함됩니다 aa21.

내가 시도한 awk 명령은 다음과 같습니다.

awk --csv 'NR==1 {for (i=1; i<=NF; i++) if ($i ~ /a2/) print $i}' file.csv 

그러나 이는 일치하는 헤더만 반환하고 그 아래 열은 반환하지 않습니다. 올바른 방향을 알려주세요. 저는 리눅스 시스템을 사용하고 있습니다.

답변1

mlr이 가짜 CSV 형식은 실제로 지원되며 정규식을 기반으로 필드를 잘라낼 수 있습니다.

$ mlr --csv --csv-trim-leading-space --allow-ragged-csv-input cut -rf a2 your-file.csv
"aaa2, aa214",aa21,aa211
data,data,data

그러나 이는 메모리에 맞지 않는 CSV로 확장되지 않습니다. --allow-ragged-csv-input예제와 행당 필드 수가 다른 CSV를 처리 하려면 어떤 경우에도 파일 전체를 읽어서 열 수를 파악해야 합니다(헤더가 없는 열에는 숫자 헤더가 자동으로 할당됨).

답변2

GNU awk를 사용 FPAT하고 필드에 개행 문자가 포함되어 있지 않다고 가정합니다.

awk -v FPAT='[^,]*|\\s*("([^"]|"")*")\\s*' -v OFS=',' '
    NR==1 {
        for ( inFldNr=1; inFldNr<=NF; inFldNr++ ) {
            if ( $inFldNr ~ /a2/ ) {
                out2in[++numOutFlds] = inFldNr
            }
        }
    }
    {
        for ( outFldNr=1; outFldNr<=numOutFlds; outFldNr++ ) {
            inFldNr = out2in[outFldNr]
            printf "%s%s", $inFldNr, (outFldNr<numOutFlds ? OFS : ORS)
        }
    }
' file.csv
 "aaa2, aa214", aa21, aa211
 data, data, data

--csv입력 파일이 유효한 CSV가 아니기 때문에(GNU awk도 필요함) 사용하지 않았습니다 ( ,s와 첫 번째 "s 사이에 공백이 있고 ,두 번째 줄 끝의 후행으로 인해 헤더보다 더 많은 데이터 열이 있음). CSV 파서가 이를 처리할 것으로 예상해서는 안 됩니다. 또한 이 문제를 해결하더라도 --csv각 열 헤더 주위의 따옴표가 제거되므로 이를 유지하고 싶은 것 같습니다. 모든 필드가 큰따옴표로 묶이지 않은 경우 약간 문제가 됩니다. 필드에 개행 문자가 포함될 수 있고 어쨌든 필드 주변의 따옴표를 제거하려는 경우에는 --csv올바른 설정을 사용하는 것보다 를 사용하는 것이 훨씬 더 좋습니다 .FPAT

정말로 시도해 보고 싶다면 --csv다음(테스트되지 않음)이 도움이 될 수 있습니다.

awk --csv -v OFS=',' '
    NR==1 {
        for ( inFldNr=1; inFldNr<=NF; inFldNr++ ) {
            if ( $inFldNr ~ /a2/ ) {
                out2in[++numOutFlds] = inFldNr
            }
        }
    }
    {
        for ( outFldNr=1; outFldNr<=numOutFlds; outFldNr++ ) {
            inFldNr = out2in[outFldNr]
            outVal = $inFldNr
            if ( outVal ~ ("[" OFS ORS "\"]") ) {
                gsub(/"/,"\"\"",outVal)
                outVal = "\"" outVal "\""
            }
            printf "%s%s", outVal, (outFldNr<numOutFlds ? OFS : ORS)
        }
    }
' file.csv

"그러나 루프에 s를 추가할 때 선행/후행 공백(있는 경우)이 처음에 따옴표 내부 또는 외부에 있었는지 알 수 있는 쉬운 방법이 없으므로 전체 필드를 인용했습니다.

바라보다awk를 사용하여 csv를 효율적으로 구문 분석하는 가장 강력한 방법은 무엇입니까CSV를 구문 분석하기 위해 awk를 사용하는 방법에 대한 추가 정보.

답변3

사용행복하다(이전 Perl_6)

...Raku의 Text::CSV모듈을 사용하여:

~$ raku -MText::CSV -e 'csv(in => csv(in => $*IN, sep => ", "), out => $*OUT);'  <  file

위의 명령은 CSV 파일(모든 열)을 메모리로 읽습니다. 파일은 std-in을 통해 수신되고 모든 열은 $*INstd-out을 통해 출력됩니다. $*OUT사용자 정의 ", "필드 구분 기호를 참고하세요.

특정 열을 필터링하려면(다른 모든 열 제거) 발견된 열의 숫자 인덱스를 반환하는 Raku의 grep키 매개변수를 사용합니다.:k

~$ raku -MText::CSV -e 'my  @aoa = csv(in => $*IN, sep => ", ");
                        my  @col-nbrs = @aoa[0].grep(/a2/, :k);
                        for @aoa.map( *.[@col-nbrs]) {
                            .map(q["] ~ * ~ q["]).join("\t").put
                        };'  <  file

입력 예:

aaa1_a, "aaa2, aa214_b", aa21_c, "aa, a14_d", aa211_e, aa44_f, aaa445_g
data1_a, data1_b, data1_c, data1_d, data1_e, data1_f, data1_g,
data2_a, data2_b, data2_c, data2_d, data2_e, data2_f, data2_g,

출력 예(위의 TSV):

"aaa2, aa214_b" "aa21_c"    "aa211_e"
"data1_b"   "data1_c"   "data1_e"
"data2_b"   "data2_c"   "data2_e"

위의 내용에는 "a2"와 일치하는 모든 열 이름이 포함되어 있습니다. 출력을 더 쉽게 읽을 수 있도록 각 필드의 텍스트를 따옴표로 묶은 다음 탭 join에서 편집합니다 \t. 따옴표가 없으면 코드가 훨씬 간단해집니다. 다음 열이 연결되어 \t\t출력됩니다.

~$ raku -MText::CSV -e 'my  @aoa = csv(in => $*IN, sep => ", ");
                        my  @col-nbrs = @aoa[0].grep(/a2/, :k);
                        for @aoa.map( *.[@col-nbrs]) {
                            $_.join("\t\t").put
                        };'  <  file
aaa2, aa214_b       aa21_c      aa211_e
data1_b     data1_c     data1_e
data2_b     data2_c     data2_e

마지막으로 기본적으로 1. 열의 쉼표 , 2. 공백이 포함된 큰따옴표 필드와 Text::CSV같은 출력 기능을 활용할 수 있습니다 .join,

% raku -MText::CSV -e 'my  @aoa = csv(in => $*IN, sep => ", ");
                       my  @col-nbrs = @aoa[0].grep(/a2/, :k);
                       my  @filtered; for @aoa.map( *.[@col-nbrs] ) {
                           @filtered.push($_); 
                       };  csv(in => @filtered, out => $*OUT);'  <  file
"aaa2, aa214_b",aa21_c,aa211_e
data1_b,data1_c,data1_e
data2_b,data2_c,data2_e

https://raku.land/zef:Tux/Text::CSV
https://github.com/Tux/CSV/blob/master/doc/Text-CSV.md
https://docs.raku.org
https://raku.org

답변4

또 다른 매우 편리한 도구는오리 데이터베이스. 달리기

duckdb --csv -c "SELECT COLUMNS('.*a2.*') from read_csv_auto('input.csv',HEADER = true)" >output.csv

당신은 얻는다

AAA2,AAA214 AA21 AA211
데이터 데이터 데이터
데이터 데이터 데이터

관련 정보