我有一个以下格式的数据:
2
1
A 11.364500 48.395199 40.160500 5
B 35.067699 30.944700 24.155300 4
4
2
A 26.274099 38.533298 32.624298 4
B 36.459000 29.662701 17.806299 5
A 15.850300 28.130899 24.753901 4
A 32.098000 33.665699 20.372499 4
5
3
A 17.501801 44.279202 8.005670 5
B 35.853001 43.095901 17.402901 4
B 1.326100 17.127600 39.600300 4
A 9.837760 41.103199 13.062300 5
B 31.686800 44.997501 16.619499 4
3
4
B 31.274700 8.726580 25.267599 4
A 19.032400 41.384701 19.456301 5
B 19.441900 24.286400 6.961680 4
1
5
B 48.973999 15.508400 5.099710 4
6
6
A 42.586601 21.343800 23.280701 5
B 30.145800 13.256200 30.713800 4
B 29.186001 44.353699 9.057160 4
A 39.311199 27.371201 35.473999 5
B 31.437799 30.415001 37.454399 4
B 48.501999 1.277730 21.900600 4
...
数据由多个重复的数据块组成。每个数据块具有相同的格式,如下所示:
First line = Number of lines of data block
Second line = ID of data block
From third line = Actual data lines (with number of lines designated in the first line)
例如,如果我们看到第一个数据块:
2 (=> This data block has 2 number of lines)
1 (=> This data block's ID is 1 (very first data block))
A 11.364500 48.395199 40.160500 5
B 35.067699 30.944700 24.155300 4
(Third and fourth lines of the data block are actual data.
The integer of the first line, the number of lines for this data block, is 2, so the actual data consist of 2 lines.
All other data blocks follow the same format. )
- 前两行是每个重复数据块的标头。第一行表示数据块的行数,第二行是数据块的ID。
- 然后,真实数据来自每个数据块的第三行。正如我所解释的,"真实数据"的行数在第一行中被指定为整数。
- 因此,每个数据块的行总数将为 number_of_lines+2。在此示例中,数据块 1 的总行数为 4,数据块 2 的成本为 6 行...
此格式重复 100,000 次。换句话说,我在单个文本文件中有 100,000 个数据块,就像此示例一样。我可以提供数据块的总数。
我希望做的是按第四列对每个数据块的"实际数据行"进行排序,并以与原始数据文件相同的格式打印出数据。我希望从上面的示例中实现的内容如下所示:
2
1
A 11.364500 48.395199 40.160500 5
B 35.067699 30.944700 24.155300 4
4
2
A 26.274099 38.533298 32.624298 4
A 15.850300 28.130899 24.753901 4
A 32.098000 33.665699 20.372499 4
B 36.459000 29.662701 17.806299 5
5
3
B 1.326100 17.127600 39.600300 4
B 35.853001 43.095901 17.402901 4
B 31.686800 44.997501 16.619499 4
A 9.837760 41.103199 13.062300 5
A 17.501801 44.279202 8.005670 5
3
4
B 31.274700 8.726580 25.267599 4
A 19.032400 41.384701 19.456301 5
B 19.441900 24.286400 6.961680 4
1
5
B 48.973999 15.508400 5.099710 4
6
6
B 31.437799 30.415001 37.454399 4
A 39.311199 27.371201 35.473999 5
B 30.145800 13.256200 30.713800 4
A 42.586601 21.343800 23.280701 5
B 48.501999 1.277730 21.900600 4
B 29.186001 44.353699 9.057160 4
...
我知道在 Linux 中使用第 4 列对数据进行排序的典型命令,即:
sort -gk4 data.txt > data_4sort.txt
但是,在这种情况下,我需要对每个数据块执行排序。另外,每个数据块没有统一的行数,但每个数据块的数据行数各不相同。
我真的不知道我该如何解决这个问题。我正在考虑使用带有 for 循环和/或 awk、sed、split 等的 shell 脚本,以及排序命令。但我什至不确定如何使用 for 循环来解决这个问题,或者 for 循环和 shell 脚本是否真的需要执行这种逐块排序。
这可以使用awk
和sort
的组合来完成。您可以使用awk
作为非常简单的状态机来驱动整个过程,它遵循以下规则:
- 有两种状态,指示您正在处理的线型组。 线
- 类型为短线(状态 0,一个字段)和长线(状态 1,更多字段)。
- 根据当前状态和正在处理的当前行,会发生不同的操作。
- 需要排序的项目将临时写入排序文件,并在需要时进行排序。
操作基本上是针对每一行的。
- 简而言之:
- 对于一行短行,只需打印即可。
- 否则(一长行),只用该行创建新的排序文件,将状态设置为 long。
- 否则(在长状态下):
- 如果行很长,只需将其附加到排序文件即可。
- 否则(短行),对文件进行排序,打印短行,将状态设置为短。
最后,如果需要,可以排序/打印/删除排序文件(因为在最后一个块之后不会有从长行到短行的最终过渡,此操作会处理这个问题)。
该处理按如下方式实现,这是一个调用awk
来完成工作的 shell 脚本:
awk '
function sort() {
close(tmp_file)
system("sort -rnk4 "tmp_file"; rm -rf "tmp_file)
}
BEGIN { tmp_file = "/tmp/tmp_file" }
state == 0 && NF == 1 { print ; next }
state == 0 && NF != 1 { print >tmp_file ; state = 1 ; next }
state == 1 && NF != 1 { print >>tmp_file ; next }
state == 1 && NF == 1 { sort() ; print ; state = 0 ; next }
END { if (state == 1) { sort() } }
' input_file
您也可以在没有具有足够高级的awk
变体的临时文件的情况下执行此操作,该文件允许管道:
awk 'NF > 1 { print | "sort -rnk4"; next }
{ close("sort -rnk4"); print }
' input_file
这样做的优点是一个更简单、更短的awk
程序,它隐式地执行了上一个脚本中的大部分内容。
在输入文件上运行这些脚本中的任何一个都会根据请求生成在第四列(最终浮点数)上排序的每个单独分组:
2
1
A 11.364500 48.395199 40.160500 5
B 35.067699 30.944700 24.155300 4
4
2
A 26.274099 38.533298 32.624298 4
A 15.850300 28.130899 24.753901 4
A 32.098000 33.665699 20.372499 4
B 36.459000 29.662701 17.806299 5
5
3
B 1.326100 17.127600 39.600300 4
B 35.853001 43.095901 17.402901 4
B 31.686800 44.997501 16.619499 4
A 9.837760 41.103199 13.062300 5
A 17.501801 44.279202 8.005670 5
3
4
B 31.274700 8.726580 25.267599 4
A 19.032400 41.384701 19.456301 5
B 19.441900 24.286400 6.961680 4
1
5
B 48.973999 15.508400 5.099710 4
6
6
B 31.437799 30.415001 37.454399 4
A 39.311199 27.371201 35.473999 5
B 30.145800 13.256200 30.713800 4
A 42.586601 21.343800 23.280701 5
B 48.501999 1.277730 21.900600 4
B 29.186001 44.353699 9.057160 4
使用 awk 准备要排序的文本,然后将其通过管道传输到 1 次调用sort
,不生成子 shell,也不创建临时文件:
$ cat tst.sh
#!/usr/bin/env bash
awk '
BEGIN { OFS="t"; recNr=1 }
NF < pNF { ++recNr }
{
print recNr, NF, NR, $0
pNF = NF
}
' "${@:--}" |
sort -k1,1n -k2,2n -k7,7nr -k3,3n |
cut -f4-
$ ./tst.sh file
2
1
A 11.364500 48.395199 40.160500 5
B 35.067699 30.944700 24.155300 4
4
2
A 26.274099 38.533298 32.624298 4
A 15.850300 28.130899 24.753901 4
A 32.098000 33.665699 20.372499 4
B 36.459000 29.662701 17.806299 5
5
3
B 1.326100 17.127600 39.600300 4
B 35.853001 43.095901 17.402901 4
B 31.686800 44.997501 16.619499 4
A 9.837760 41.103199 13.062300 5
A 17.501801 44.279202 8.005670 5
3
4
B 31.274700 8.726580 25.267599 4
A 19.032400 41.384701 19.456301 5
B 19.441900 24.286400 6.961680 4
1
5
B 48.973999 15.508400 5.099710 4
6
6
B 31.437799 30.415001 37.454399 4
A 39.311199 27.371201 35.473999 5
B 30.145800 13.256200 30.713800 4
A 42.586601 21.343800 23.280701 5
B 48.501999 1.277730 21.900600 4
B 29.186001 44.353699 9.057160 4
以下是上述 OPs 输入文件执行速度的第三次运行时序差异:
$ time ./tst.sh file >/dev/null
real 0m0.105s
user 0m0.030s
sys 0m0.091s
当前的awk答案为每个要排序的输入块生成一个子shell:
@IceCreamTucan的回答是:
$ cat tst2.sh
#!/usr/bin/env bash
awk '{
if(NF > 1){
print | "sort -k4,4nr"
}
else{
close("sort -k4,4nr")
print
}
}' "${@:--}"
$ time ./tst2.sh file >/dev/null
real 0m0.405s
user 0m0.120s
sys 0m0.121s
@paxdiablo的回答是:
$ cat tst3.sh
#!/usr/bin/env bash
awk '
function sort() {
close(tmp_file)
system("sort -rnk4 "tmp_file"; rm -rf "tmp_file)
}
BEGIN { tmp_file = "/tmp/tmp_file" }
state == 0 && NF == 1 { print ; next }
state == 0 && NF != 1 { print >tmp_file ; state = 1 ; next }
state == 1 && NF != 1 { print >>tmp_file ; next }
state == 1 && NF == 1 { sort() ; print ; state = 0 ; next }
END { if (state == 1) { sort() } }
' "${@:--}"
$ time ./tst3.sh file >/dev/null
real 0m0.804s
user 0m0.060s
sys 0m0.396s
一个选项,用于通过管道传输到命令,如下所示,而无需显式创建临时文件。
awk '
BEGIN {
cmd = "sort -k4,4nr"
}
{
if(NF > 1){
print | cmd
}
else{
close(cmd)
print
}
}' input_file
注意:更改为数字排序并将cmd移动到变量,这要归功于@Ed莫顿的有用提示
输出:
2
1
A 11.364500 48.395199 40.160500 5
B 35.067699 30.944700 24.155300 4
4
2
A 26.274099 38.533298 32.624298 4
A 15.850300 28.130899 24.753901 4
A 32.098000 33.665699 20.372499 4
B 36.459000 29.662701 17.806299 5
5
3
B 1.326100 17.127600 39.600300 4
B 35.853001 43.095901 17.402901 4
B 31.686800 44.997501 16.619499 4
A 9.837760 41.103199 13.062300 5
A 17.501801 44.279202 8.005670 5
3
4
B 31.274700 8.726580 25.267599 4
A 19.032400 41.384701 19.456301 5
B 19.441900 24.286400 6.961680 4
1
5
B 48.973999 15.508400 5.099710 4
6
6
B 31.437799 30.415001 37.454399 4
A 39.311199 27.371201 35.473999 5
B 30.145800 13.256200 30.713800 4
A 42.586601 21.343800 23.280701 5
B 48.501999 1.277730 21.900600 4
B 29.186001 44.353699 9.057160 4
我知道,老问题但是...另一个想法,使用更新的gawk
能力性能更高......
一个想法使用gawk
及其预定义的阵列扫描顺序(即,消除对外部sort
的需求)...
awk '
BEGIN { delete arr } # hack to pre-declare variable arr as an array
function sort_block() {
if ( length(arr) > 0 ) { # if array is not empty then ...
PROCINFO["sorted_in"]="@ind_num_desc" # desginate sorting by index/descending and then ...
for (i in arr) # loop through array ...
print arr[i] # printing arry entries
}
delete arr # clear array
}
NF==1 { sort_block()
print
}
NF>1 { arr[$4]=$0
next
}
END { sort_block() } # flush anything still in array
' block.dat
针对 OPs 示例数据集运行此命令会生成:
2
1
A 11.364500 48.395199 40.160500 5
B 35.067699 30.944700 24.155300 4
4
2
A 26.274099 38.533298 32.624298 4
A 15.850300 28.130899 24.753901 4
A 32.098000 33.665699 20.372499 4
B 36.459000 29.662701 17.806299 5
5
3
B 1.326100 17.127600 39.600300 4
B 35.853001 43.095901 17.402901 4
B 31.686800 44.997501 16.619499 4
A 9.837760 41.103199 13.062300 5
A 17.501801 44.279202 8.005670 5
3
4
B 31.274700 8.726580 25.267599 4
A 19.032400 41.384701 19.456301 5
B 19.441900 24.286400 6.961680 4
1
5
B 48.973999 15.508400 5.099710 4
6
6
B 31.437799 30.415001 37.454399 4
A 39.311199 27.371201 35.473999 5
B 30.145800 13.256200 30.713800 4
A 42.586601 21.343800 23.280701 5
B 48.501999 1.277730 21.900600 4
B 29.186001 44.353699 9.057160 4
在笔记本电脑级 i7 上的 VM 内的cygwin
环境中重复运行此代码(即,这绝不是一个快速系统)可提供以下最佳时机:
real 0m0.026s
user 0m0.000s
sys 0m0.015s