다음과 같이 만들 수 있는 두 개의 열 파일이 있습니다.
cat > twocol << EOF
007 03
001 03
003 01
137 12
001 11
002 01
002 02
002 03
001 02
002 04
137 94
010 21
001 01
EOF
생성된 파일에는 twocol
수직선만 포함되어 있습니다.
원하는 결과
twocol
어떤 종류의 명령을 실행 하고 다음 결과를 얻고 싶습니다 . (다소 혼란스러운 질문 제목을 다시 설명하는 것보다 보는 것이 훨씬 낫다고 생각합니다. "첫 번째 열을 기준으로 정렬한 다음 두 번째 열을 기준으로 정렬합니다. 첫 번째 열만 한 번 출력하고 두 번째 열을 모두 출력합니다."
001 01
02
03
11
002 01
02
03
04
003 01
007 03
010 21
137 12
94
이것은 sort
단순한 것이 나에게 제공하는 것과 다릅니다.
001 01
001 02
001 03
001 11
002 01
002 02
002 03
002 04
003 01
007 03
010 21
137 12
137 94
나는 일한다
내가 생각할 수 있는 유일한 해결책은 (괜찮은 스크립트를 얻기 전에) 내가 생각해낸 첫 번째 솔루션 입니다 awk
. 몇 가지 인스턴스 awk
, 무리 bash
및 다음의 도움을 사용하여 위에서 굵게 표시된 원하는 결과와 일치합니다.1.
col_1_max_len=$(awk '
BEGIN{maxl=0;}
{curr=length($1);max1=max1>curr?max1:curr;}
END{print max1}' \
twocol);
len1=$col_1_max_len;
len2=$(awk '
BEGIN{max2=0;}
{curr=length($2);max2=max2>curr?max2:curr;}
END{print max2}' \
twocol);
current_col_1_val="nothing";
while read -r line; do {
current_row="${line}";
col_1_val=$(awk '{print $1}' <<< "${current_row}");
col_2_val=$(awk '{print $2}' <<< "${current_row}");
if [ ! "${col_1_val}" == "${current_col_1_val}" ]; then
printf "%0"$len1"d %0"$len2"d\n" "${col_1_val}" "${col_2_val}";
else
printf "%"$len1"s %0"$len2"d\n" " " "${col_2_val}";
fi;
}; done < <(sort twocol)
awk
이 답변과 비슷하게 단일 패스를 사용할 수 있어야 한다고 생각합니다 .2,삼,4,5, ...
추가로 부피가 크고 메모리를 많이 소비하는 배열 없이는 함께 사용할 수 없는 것 같습니다. 이 형식은 또한 나에게 문제를 제기합니다. 첫 번째와 두 번째 열의 숫자는 더 많은 자릿수를 가질 수 있으며 바람직하게는 좋아 보입니다.
누구든지 좋은 방법으로 이 결과를 얻는 방법을 말해 줄 수 있습니까? awk
코드 - 터미널에서 쉽게 사용할 수 있는 것이 바람직합니까? Perl
답변도 환영합니다.
아 내 시스템
$ uname -a && bash --version | head -1 && awk --version | head -1
CYGWIN_NT-10.0 MY-MACHINE 3.2.0(0.340/5/3) 2021-03-29 08:42 x86_64 Cygwin
GNU bash, version 4.4.12(3)-release (x86_64-unknown-cygwin)
GNU Awk 5.1.0, API: 3.0 (GNU MPFR 4.2.0-p9, GNU MP 6.2.1)
(Fedora와 Ubuntu 시스템에서 똑같은 동작이 나타납니다.)
편집하다
나는 해결책을 생각해 냈습니다 awk
. 모든 것이 괜찮아 보이고 짧아 보이지만 여전히 뭔가 잘못된 것 같습니다.
awk '{if (!vals[$1]++) print($0); else print(" ",$2);}' <(sort twocol)
나는 배열에서 많은 메모리를 사용하고 있다고 생각합니다 vals
. 현재 내 파일은 약 10,000행에 불과하지만 더 크게 만들고 싶습니다. 형식으로 하드코딩하고 있지만 길이가 다른 문자열을 가질 수 있기 때문에 마음에 들지 않습니다.
변수를 사용하여 세 번 수행하고 변수를 전달하면 awk
이(형식)을 수정할 수 있습니다 .
length1=$(awk '
BEGIN{maxl=0;}
{curr=length($1);max1=max1>curr?max1:curr;}
END{print max1}' \
twocol);
length2=$(awk '
BEGIN{max2=0;}
{curr=length($2);max2=max2>curr?max2:curr;}
END{print max2}' \
twocol);
awk -vlen1=$length1 -vlen2=$length2 '
{
if (!vals[$1]++)
printf("%0*d %0*d\n",len1,$1,len2,$2);
else
printf("%*s %0*d\n",len1," ",len2,$2);
}' <(sort twocol)
결과는 내가 원하는 것과 정확히 일치하지만(위의 굵은 글씨 참조) 한 번에 통과할 수 있는 방법이 있기를 바랐습니다 awk
.
내가 언급한 특성에 맞는 것을 공유할 수 있는 사람이 있나요? 다양한 접근 방식의 시간 성능 및/또는 메모리 성능에 대한 의견도 감사하겠습니다.
나는 정렬도 할 수 있다고 생각합니다 awk
. 특히 그것이 더 효율적일 수 있는지 궁금합니다.편집하다:@steeldriver 및 @markp-fuso가 아래에 표시된 것처럼 이 작업을 수행할 수 있습니다.
답변1
원래 awk 솔루션이 제거되었습니다 - a더 나은 솔루션게시됨
실제로 입력을 미리 정렬한 다음 awk를 사용하여 형식을 지정할 수 있습니다.
sort twocol | awk 'BEGIN{OFS="\t"} {print $1 == last ? "" : $1, $2; last = $1}'
001 01
02
03
11
002 01
02
03
04
003 01
007 03
010 21
137 12
94
이렇게 하면 탭으로 구분된 출력이 생성됩니다. 공백이 필요한 경우 결과를 파이프합니다 expand
.
또는 익명 배열의 Perl 해시를 사용하여 두 번째 열 값을 집계한 다음 정렬하고 인쇄할 수 있습니다.
perl -alne '
push @{ $h{$F[0]} }, $F[1]
}{
foreach $k (sort {$a <=> $b} keys %h) {
@a = sort {$a <=> $b} @{ $h{$k} };
print join "\n", map { ($_ == 0 ? $k : "") . "\t" . $a[$_] } 0..$#a;
}
' twocol
001 01
02
03
11
002 01
02
03
04
003 01
007 03
010 21
137 12
94
{$a <=> $b}
제로 패딩된 데이터를 사전식으로 정렬하는 것은 숫자로 정렬하는 것과 동일하므로 이러한 작업은 필요하지 않을 수 있습니다.
단지 재미로,밀러:
mlr -S --nidx --ofs tab put -q '
@m[$1] = is_not_array(@m[$1]) ? [$2] : append(@m[$1],$2);
end {
@m = sort(apply(@m, func(k,v) { return {k: joinv(sort(v), "\n\t")}; }));
emit @m, ""
}
' twocol
001 01
02
03
11
002 01
02
03
04
003 01
007 03
010 21
137 12
94
답변2
아이디어 awk
:
awk '
BEGIN { OFS="\t" }
{ a[$1][$2] } # we can sort on both indices to obtain the desired ordering
END { PROCINFO["sorted_in"] = "@ind_num_asc" # applies to all follow-on array references (ie, both indices of the a[] array)
for (i in a) {
firstcol = i
for (j in a[i]) {
print firstcol, j
firstcol = ""
}
}
}
' twocol
노트:이건 지원 이 GNU awk 4.0+
필요해PROCINFO["sorted_in"]
그러면 다음이 생성됩니다.
001 01
02
03
11
002 01
02
03
04
003 01
007 03
010 21
137 12
94
PROCINFO["sorted_in"]
사용할 수 없는 경우 sort
이를 사용하여 단순화된 스크립트를 제공 할 수 있습니다 awk
.
awk '
BEGIN { OFS="\t" }
{ if ($1 != prev1) {
print $1,$2
prev1 = $1
}
else
print "",$2
}
' < <(sort twocol)
이는 또한 다음을 생성합니다.
001 01
02
03
11
002 01
02
03
04
003 01
007 03
010 21
137 12
94
답변3
사용행복하다(이전 Perl_6)
~$ raku -ne 'BEGIN my %h; %h.append: .split(/ \s+ /); END put .key => .value.sort.join("\n\t") for %h.sort;' file
#OR
~$ raku -ne 'BEGIN my %h; %h.append: .words; END put .key => .value.sort.join("\n\t") for %h.sort;' file
이것은 Perl 프로그래밍 언어 중 하나인 Raku로 작성된 답변입니다. 간단히 말해서, 위의 코드는 awk
- 와 같은 작업을 수행하고 Raku(Perl과 같은)의 -ne
비자동 인쇄 명령줄 플래그를 사용합니다.
- 해시 값은
%h
블록으로 선언됩니다BEGIN
. - 줄이
.split
하나 이상의\s
공백 문자 위에 있습니다. 또는 (두 번째 답변).words
공백으로 분할하는 Raku의 루틴입니다. 두 답변 모두에서 결과(2개) 요소는append
해시로 컴파일되는 키-값 쌍으로 이해됩니다 . END
블록 내에서%h
해시 값(sort
키에 포함)은 개별적으로 출력되며put
, 각 값.key
뒤에는.value
이미 존재하는 각 값이 옵니다sort.join("\n\t")
. 다음 행으로 넘어가는 값을\t
두 번째 열로 이동합니다.
입력 예:
007 03
001 03
003 01
137 12
001 11
002 01
002 02
002 03
001 02
002 04
137 94
010 21
001 01
예제 출력:
001 01
02
03
11
002 01
02
03
04
003 01
007 03
010 21
137 12
94
때로는 Raku의 기본값을 보는 것이 유익할 수 있으므로 위의 출력을 "열로 표시"하지 않은 답변은 다음과 같습니다(즉, 아래의 더 간단한 코드).
~$ raku -ne 'BEGIN my %h; %h.append: .words; END say .key => .value.sort for %h.sort;' file
001 => (01 02 03 11)
002 => (01 02 03 04)
003 => (01)
007 => (03)
010 => (21)
137 => (12 94)