我经常遇到需要对部分有序数据进行排序的情况。第一列已经排序,后面的列尚未排序。就像这个两列示例:
1 5
1 3
2 10
2 -1
2 3
3 11
3 -200
3 20
期望的输出是
sort -k 1,1g -k 2,2g
这种方法可行,但存在一个问题,即在读取完所有输入之前,排序不会产生任何结果。当输入是几 GB 的文本时,这可能需要一段时间,在此期间,管道中排序的下游部分无法执行。从内存使用率来看,这种方法也不是很高效,因为整个数据集必须同时驻留在其中,尽管为了实现所需的排序,实际上只需要一小部分数据就足够了。
使用脚本,将其分解成块然后对每个块进行排序并不困难。sort 命令是否有一个选项可以通知它该列中的数据已经排序?我在 sort 8.4 中没有看到它,但也许我只是错过了它?
如果排序在已排序的列中遇到无序值,则排序应退出。这表示上游处理中出现错误。
答案1
我无法用 sole 做到这一点sort
。这可能行不通。
在我的解决方案中,awk
处理第一列并sort
根据需要运行多次。脚本从 stdin 获取输入,然后打印到 stdout。
#!/usr/bin/awk -f
BEGIN { command = "sort -k 2,2g" }
{
if ( NR==1 ) {
val=$1
buf=$0
}
else
if ( $1 < val ) {
print "Unsorted 1st column detected. Processing last valid chunk and aborting." > "/dev/stderr"
exit 1
}
else {
if ( $1 == val )
buf=buf"\n"$0
else
{
print buf | command
close(command)
buf=$0
val=$1
}
}
}
END { print buf | command }
笔记:
close(command)
至关重要。没有它,所有管道command
都会通向单身的sort
。- 我认为
awk
的比较运算符可以很好地处理数字。真的确保解决方案能够正常sort
工作,您需要分别检索 和sort -c -k 1,1g
的退出状态,然后在此基础上构建脚本逻辑。这将在每个输入行上运行两个进程,我预计性能会受到很大影响。val"\n"$1
$1"\n"val
sort
答案2
Perl 来救援!
它完全按照您的要求进行操作,它将以相同数字开头的输入块传输到仅对第二列进行操作的排序。
#!/usr/bin/perl
use warnings;
use strict;
my $sort;
my $first = -1;
while (<>) {
my ($x, $y) = split;
if ($first != $x) {
die "Unsorted line $." if $first > $x;
$first = $x;
open $sort, '|-', 'sort -k2,2n' or die $!;
}
print {$sort} $_;
}
唯一的问题可能是初始值$first
:如果您的输入在第一列以负数开头,则需要指定一个较小的值。
我使用了n
排序而不是g
因为它在我的计算机上似乎更快一些。
答案3
Kamil Cuk 在另一个帖子中发布了这个答案。我现在要尝试删除那个帖子,因为显然在两个地方都发布这个答案是不行的,我想在这里保留这个答案。
以下脚本将具有相同第一列的行打印到临时文件中并对其进行排序。
file=$1
# create temporary file
temp=$(mktemp)
trap 'rm "$temp"' EXIT
i_last=""
while read -r i j; do
if [ "$i" != "$i_last" ]; then
# output sorted temporary file
sort -n $temp
# and truncate temporary file
> $temp
# increment first column pointer
i_last=$i
fi
# print all lines into temporary file
echo "$i" "$j" >>$temp
done <"$file"
# dont forget leftovers
sort -n "$temp"