次のように作成できる2つの列ファイルがあります。
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、...
さらに、かさばり、メモリを大量に消費する配列なしでは一緒に使用できないようです。この形式はまた私に問題を提起します。 1列目と2列目の数字は、より多くの桁数を持つことができ、好ましくは良く見える。
誰でも良い方法でこの結果を得る方法を教えてもらえますか? 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行にすぎませんが、もっと大きくしたいと思います。フォーマットでハードコードしていますが、長さが異なる文字列を持つことができるので気に入らません。
変数を使用して3回実行して変数を渡すと、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ハッシュを使用して2番目の列値を集計し、ソートして印刷することもできます。
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プログラミング言語の1つであるRakuで書かれた答えです。簡単に言えば、上記のコードはawk
- と同じことを行い、Raku(Perlなど)の-ne
非自動印刷コマンドラインフラグを使用します。
- ハッシュ値は
%h
ブロックとして宣言されますBEGIN
。 - 行が
.split
1 つ以上の\s
空白文字の上にあります。または(2番目の答え)は、.words
スペースに分割するRakuのルーチンです。どちらの回答でも、結果(2つ)要素はappend
ハッシュとしてコンパイルされるキーと値のペアとして理解されます。 END
ブロック内では、%h
ハッシュ値(sort
キーに含まれる)は個別に出力され、各put
値.key
の後に.value
はすでに存在する各値が続きますsort.join("\n\t")
。次の行に進む値を\t
2番目の列に移動します。
入力例:
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)