Perl 또는 bash에서 텍스트 파일 집계 및 그룹화

Perl 또는 bash에서 텍스트 파일 집계 및 그룹화

;다음 형식(4열, 로 구분 ) 의 큰 텍스트 파일(5m 줄)이 있습니다 .

문자열 1; 문자열 3;

이것처음 3개의 문자열(SHA1)이라는 단일 ID를 형성합니다.애플리케이션 ID(그래서 이렇게 단순화할 수 있습니다 :) appId; userId. 두 번째 열(string2 또는 두 번째 부분애플리케이션 ID) 자체는 쉼표로 구분된 여러 부분으로 구성될 수 있습니다 ,. 파일이 정렬되었습니다.

다음과 같이 각 애플리케이션에 대한 사용자 목록을 갖고 싶습니다.

입력하다문서:

app1, user1
app1, user2
app1, user3
app2, user1

산출문서:

app1: user1, user2, user3
app2: user1

"진짜"의 일부입력하다문서:

44a934ca4052b34e70f9cb03f3399c6f065becd0;bf038823f9633d25034220b9f10b68dd8c16d867;309;8ead5b3e0af5b948a6b09916bd271f18fe2678aa
44a934ca4052b34e70f9cb03f3399c6f065becd0;bf038823f9633d25034220b9f10b68dd8c16d867;309;a21245497cd0520818f8b14d6e405040f2fa8bc0
5c3eb56d91a77d6ee5217009732ff421e378f298;200000000000000001000000200000,6fd299187a5c347fe7eaab516aca72295faac2ad,e25ba62bbd53a72beb39619f309a06386dd381d035de372c85d70176c339d6f4;16;337556fc485cd094684a72ed01536030bdfae5bb
5c3eb56d91a77d6ee5217009732ff421e378f298;200000000000000001000000200000,6fd299187a5c347fe7eaab516aca72295faac2ad,e25ba62bbd53a72beb39619f309a06386dd381d035de372c85d70176c339d6f4;16;382f3aaa9a0347d3af9b35642d09421f9221ef7d
5c3eb56d91a77d6ee5217009732ff421e378f298;200000000000000001000000200000,6fd299187a5c347fe7eaab516aca72295faac2ad,e25ba62bbd53a72beb39619f309a06386dd381d035de372c85d70176c339d6f4;16;396529e08c6f8a98a327ee28c38baaf5e7846d14

"진짜"산출파일은 다음과 같아야 합니다.

44a934ca4052b34e70f9cb03f3399c6f065becd0;bf038823f9633d25034220b9f10b68dd8c16d867;309:8ead5b3e0af5b948a6b09916bd271f18fe2678aa, a21245497cd0520818f8b14d6e405040f2fa8bc0
5c3eb56d91a77d6ee5217009732ff421e378f298;200000000000000001000000200000,6fd299187a5c347fe7eaab516aca72295faac2ad,e25ba62bbd53a72beb39619f309a06386dd381d035de372c85d70176c339d6f4;16:337556fc485cd094684a72ed01536030bdfae5bb, 382f3aaa9a0347d3af9b35642d09421f9221ef7d, 396529e08c6f8a98a327ee28c38baaf5e7846d14

어떻게 해야 하나요?


편집하다:또한 각 애플리케이션에는 수천 명의 사용자가 있을 수 있는데, 대기열은 얼마나 길어질 수 있습니까? 줄 길이에 제한이 있나요?

답변1

펄에서

perl -F';' -lane 'push @{$h{join ";",@F[0..2]}},$F[3];
                  END{
                    for(sort keys %h){
                        print "$_: ". join ",",@{$h{$_}};
                    }
                  }' your_file

연관 배열을 사용하여 비슷한 작업을 수행할 수 있어야 awk하지만 저는 이에 대해 잘 알지 못하므로 awk실제 코드에 기여할 수 없습니다.

설명하다

다음은 "마법"을 가능한 한 적게 사용하는 위 코드의 확장 버전입니다.

open($FH,"<","your_file");
while($line=<$FH>){ # For each line in the file (accomplished by -n)
    chomp $line; # Remove the newline at the end (done by -l)
    # The ; is set by -F and storing the split in @F done by -a
    @F = split /;/,$line # Split the line into fields on ;
    $app_id = join ";",@F[0..2]; # AppID is the first 3 fields
    push @{$h{$app_id}},$F[3]; # The 4th field is added onto the hash
} # The whole file has been read at this point.
foreach $key (sort keys %h){ # Sort the hash by AppID
     print "$key: " . join ",",@{h{$key}}."\n"; # Print the array values
     # The newline ("\n") added at the end is also done by -l
}

이제 push이 문장만 더 자세히 설명하면 됩니다.

  • push일반적으로 배열 변수에 요소를 추가하는 데 사용됩니다. 예를 들어:

    push @a,$x
    

    변수의 내용을 $x배열에 추가합니다 @a.

  • 파일을 한 줄씩 읽는 루프가 해시 테이블( %h)을 채우고 있습니다. 해시의 키는 AppID이고, 각 키에 해당하는 값은 해당 AppID와 연결된 모든 사용자 ID를 포함하는 배열입니다. 이것은 익명 배열입니다(이름이 없음). Perl에서는 배열 참조로 구현됩니다(C 포인터와 다소 유사). %hAppID에 해당하는 값은 $app_id으로 표시되므로 Perl $h{$app_id}배열 sigial( @)을 추가하면 해시 값을 배열로 처리하고(배열 참조 역참조) 현재 사용자 ID를 여기에 푸시합니다.

  • 덜 "Perlish"처럼 느껴질 수 있는 또 다른 대안은 네 번째 필드를 현재 값에 연결하는 것입니다.

    while(...) { ... $h{$app_id} = $h{$app_id} . ",$F[3]" }
    foreach $key (sort keys %h) { print "$_: $h{$_}" }
    

    Perl 에서는 .문자열 연결 연산자입니다.

설명된 코드에서는 perl -e '...'구문 강조 표시가 코드에 도달하여 더 쉽게 읽을 수 있도록 래퍼를 생략했습니다.

답변2

appId파일이 정렬되었다고 말씀하셨는데, 간단한 루프를 사용하여 이전 문자열의 메모리 만 저장하는 것이 가능합니까 ? @Qeole의 접근 방식과 약간 비슷하지만 쉘의 구분 기호 기능과 문자열 비교를 사용하여 sed정규식 오버헤드를 피합니다 .read

#!/bin/bash

appId=""

while IFS=\; read -r s1 s2 s3 userId; do
  if [ "$s1;$s2;$s3" == "$appId" ]; then
    printf ', %s' "$userId"
  else
    appId="$s1;$s2;$s3"
    printf '\n%s:%s' "$appId" "$userId"
  fi
done < yourfile
printf '\n'

참고: 이렇게 하면 출력 시작 부분에 추가 줄바꿈이 인쇄되지만 추가 복잡성을 최소화하면 이를 방지할 수 있습니다. 큰 타격을 입어야 한다상당히이런 종류의 작업에는 빠르지만 그렇지 않은 경우 거의 모든 유사한 스크립팅 언어로 다시 구현할 수 있습니다.

답변3

그리고 sed:

sed 's/;/:\t/3;H;1h;x                                                                                        
s/^\(\([^:]*\):.*\)\n\2/\1/                                                                                      
/\n/P;//g;h;$!d' <input |
tr : \\n

인쇄:

44a934ca4052b34e70f9cb03f3399c6f065becd0;bf038823f9633d25034220b9f10b68dd8c16d867;309
        8ead5b3e0af5b948a6b09916bd271f18fe2678aa
        a21245497cd0520818f8b14d6e405040f2fa8bc0
5c3eb56d91a77d6ee5217009732ff421e378f298;200000000000000001000000200000,6fd299187a5c347fe7eaab516aca72295faac2ad,e25ba62bbd53a72beb39619f309a06386dd381d035de372c85d70176c339d6f4;16
        337556fc485cd094684a72ed01536030bdfae5bb
        382f3aaa9a0347d3af9b35642d09421f9221ef7d
        396529e08c6f8a98a327ee28c38baaf5e7846d14

tr그룹을 같은 줄에 유지하기 위해 삭제할 수 있습니다 . 이것ID:이 경우 콜론으로 구분됩니다. \t첫 번째 줄의 이스케이프 문자를 리터럴 문자로 바꿔야 할 수도 있습니다 <tab>. 또는 Ab를 완전히 제거해도 됩니다 \t. 그러면 출력이 더 읽기 쉬워집니다.(내 생각에는)정규식의 기능에는 중요하지 않습니다.

답변4

한 가지 sed답변:

sed ': l;N;s/^\([^;]\+;[^;]\+;[^;:]\+\)[;:] *\(.*\)\n\1;\(.*\)/\1: \2, \3/;tl;P;D' input_file.txt

파일은 한 번만 읽어서 성능이 크게 나쁘지는 않겠지만 자세한 내용은 말씀드릴 수 없습니다.

세부:

sed ': l;        # Label l

     N;          # Add next line of input to pattern space

     s/^\([^;]\+;[^;]\+;[^;:]\+\)[;:] *\(.*\)\n\1;\(.*\)/\1: \2, \3/;
                 # If two lines in pattern space start with same AppID, then
                 # take user ID and append it to first line, then delete second line

         tl;     # If previous substitution succeeded, i.e. we scanned two lines with 
                 # same AppID, then loop to label l. Else go on…

     P;          # Print first line from pattern space (here there should be two lines
                 # in pattern space, starting with a different AppID)

     D;          # Delete first line of pattern space; start script again with
                 # remaining text in pattern space, or next input line if pattern
                 # space is empty
    ' input_file.txt

(하지만 줄 길이에 대한 잠재적인 제한에 대해서는 모르겠습니다. 죄송합니다.)

관련 정보