2017-10-13 13 views
4

몇 년 동안 (정렬 된) 텍스트와 일치하는 첫 번째 필드를 결합해야 할 필요가 있으며 우아한 (예 : 한 줄짜리 유닉스 명령 행) 방법을 찾지 못했습니다. 해. 내가 원했던 것은 유닉스 join 명령으로 가능했던 것과 비슷하지만 join은 2 개의 파일이 필요하며 각 키는 최대 한 번 나타납니다. 하나의 파일로 시작하여 키가 여러 개의 타일로 나타날 수 있습니다.일치하는 첫 번째 필드가있는 행 결합

나는이 작업을 수행하는 루비와 펄 스크립트를 가지고 있지만 알고리즘을 한 줄로 줄이는 방법은 없습니다. 수년간의 유닉스 사용 후, 나는 여전히 comm, paste, uniq 등과 같은 새로운 트릭을 배우고 있으며,이를위한 현명한 방법이 있다고 생각한다.

join all lines that have the same first column to the same line과 같은 몇 가지 관련 질문이 있습니다. Command line to match lines with matching first field (sed, awk, etc.); Combine lines with matching keys -하지만 이러한 솔루션은 결코 깨끗하고 안정적인 솔루션을 제공하지 못합니다.

apple:A fruit 
apple:Type of: pie 
banana:tropical fruit 
cherry:small burgundy fruit 
cherry:1 for me to eat 
cherry:bright red 

여기 샘플 출력 :

다음은 샘플 입력의

apple:A fruit;Type of: pie 
banana:tropical fruit 
cherry:small burgundy fruit;1 for me to eat;bright red 

여기 내 이상적인 구문입니다 :

merge --inputDelimiter=":" --outputDelimiter=";" --matchfield=1 infile.txt 

은 "matchfield는"정말 선택 사항입니다. 항상 첫 번째 필드가 될 수 있습니다. 후속 구분 기호는 일반 텍스트처럼 취급해야합니다.

당신이 짧고 우아한 알고리즘을 생각할 수 있다면 나는 펄, 루비, awk 한 - 라이너 상관 없어. 이것은 수백만 줄의 입력을 처리 할 수 ​​있어야합니다. 어떤 아이디어?

답변

2

발견 AWK 언어 :

awk -F':' '{ v=substr($0, index($0,":")+1); a[$1]=($1 in a? a[$1]";" : "")v } 
      END{ for(i in a) print i,a[i] }' OFS=':' infile.txt 

출력 :

apple:A fruit;Type of: pie 
banana:tropical fruit 
cherry:small burgundy fruit;1 for me to eat;bright red 
+0

고마워요 @RomanPerekhrest, 그건 작동합니다. 복잡한 라인을 깰 수있는 과거에 시도한 다른 awk 솔루션보다 낫다. 즉, 나는 여전히 간단한 구문으로 더 짧은 명령어를 좋아할 것이지만, 한 줄짜리를 가지고 기쁘게 생각합니다. – MichaelD

1
for F in `cut -f1 -d ':' infile.txt | sort | uniq`; do echo "$F:$(grep $F infile.txt | cut -f2- -d ':' | paste -s -d ';' -)"; done 

확실하지가 '우아한'자격,하지만 난 빨리 확실하지 않다하지만 그것이 작동 수백만 라인의 경우 - grep 전화가 증가하면 전화 속도가 크게 느려질 것입니다. 일치하는 필드의 몇 %가 고유 할 것으로 예상합니까?

+0

유닉스 문자열을 보내 주셔서 감사합니다. 키/매칭 필드의 1-5 반복을 예상하므로 백만 줄에서 300k 개의 키가있을 수 있습니다. – MichaelD

+0

아, 300k grep 통화는 부당합니다. 의견을 주셔서 감사합니다 – jgrundstad

1

나는이 하나가 AWK 하나 라이너를 사용하여 작업

awk -F':' '$1!=a{if(b);print b;b=""}a=$1{$1="";if(!b)b=a;b=b$0}END{print b}' infile 
+2

설명해 주시겠습니까? – ghoti

3

을 생각

awk -F: -v ORS="" 'a!=$1{a=$1; $0=RS $0} a==$1{ sub($1":",";") } 1' file 

출력 :

apple:A fruit;Type of: pie 
banana:tropical fruit 
cherry:small burgundy fruit;1 for me to eat;bright red 

ORS=""을 설정하는 단계; 기본적으로 \n입니다.
(Output Record Separator)를 설정 한 이유는 awk가 각 레코드의 끝에서 출력에 개행을 포함시키지 않기 때문입니다. 우리는 우리 자신의 논리를 통해 우리 자신의 방식으로 처리하려고합니다.우리는 실제로 첫 번째 필드가 이전 레코드와 다른 모든 레코드의 시작 부분에 개행을 포함합니다.

a!=$1 : 변수 a (처음에는 null)이 첫 번째 필드 $1과 일치하지 않는 경우 (예 : 첫 번째 줄에 apple을 입력 한 다음 a=$1$0=RS $0, 즉 $0 또는 간단히 whole record"\n"$0 (기본적으로 레코드 시작 부분에 줄 바꿈을 추가 함)으로 설정됩니다. a!=$1은 이전 라인의 $1과 다른 첫 번째 필드 ($1)가있을 때 항상 만족하므로 첫 번째 필드를 기준으로 레코드를 분리하는 기준이됩니다.

a==$1 : 일치하면 이전 레코드 세트에 속한 레코드를 반복하는 것입니다. 이 경우 예를 들어 $1: (:에 유의하십시오)의 첫 x 째 어커런스를 대체하십시오. apple:;. $1":"$1FSFS is :

과 같이 쓸 수있다 당신이 당신의 파일에서 라인의 수백만이있는 경우는 어떠한 사전 처리를 포함하지 않으며, 또한 우리가 다른 데이터 구조가 배열 말을 사용하지 않기 때문에 다음이 방법은 가장 빠른 것 키 또는 레코드를 저장하는 데 사용됩니다.

+0

좋은 설명 주셔서 감사합니다. – MichaelD

+0

@MichaelD : Welcome Michael. – batMan