我有一个两列文件,您可以按如下方式创建
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
这与 simplesort
给我的不同,即不同于
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,3,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
- 截至目前,我的文件只有约 10k 行,但我希望将其扩大。我以格式进行硬编码,但我不喜欢它,因为我可以有不同长度的字符串。
如果我使用变量进行三遍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
这是用 Raku(Perl 编程语言家族的成员)编写的答案。简而言之,上面的代码有点awk
像 - 并使用 Raku 的(如 Perl 的)-ne
非自动打印命令行标志。
- 哈希值
%h
在块中声明BEGIN
。 - 该行位于
.split
一个或多个\s
空格字符上。或者(第二个答案),Raku 的.words
例程用于按空格进行分割。对于这两个答案,结果(两个)元素被理解为一个键值对,它被append
编辑到哈希中。 - 在
END
块中,%h
哈希值(sort
键上的 ed)单独输出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)