如何找出一个数字与文件中下一次出现的相同数字之间有多少行?
How can I find out how many lines are between a number and the next occurrence of the same number in a file?
我有很大的文件,每个文件都存储了非常长的计算结果。这是一个文件示例,其中有五个时间步长的结果; 第三、第四和第五 个时间步的输出存在问题。
(请注意,我一直很懒,在我的示例中使用了相同的数字来表示每个时间步的结果。实际上,每个时间步的数字都是唯一的。)
3
i = 1, time = 1.000, E = 1234567
Mg 22.9985897185 6.9311166109 0.7603733573
O 23.0438129644 6.4358253659 1.5992513709
O 23.8223149199 7.2029442290 0.4030956770
3
i = 2, time = 1.500, E = 1234567
Mg 22.9985897185 6.9311166109 0.7603733573
O 23.0438129644 6.4358253659 1.5992513709
O 23.8223149199 7.2029442290 0.4030956770
3
i = 3, time = 2.000, E = 1234567
Mg 22.9985897185 6.9311166109 0.7603733573
O 23.0438129644 6.4358253659 1.5992513709
O 23.8223149199 (<--Problem: calculation stopped and some numbers are missing)
3
i = 4, time = 2.500, E = 1234567
Mg 22.9985897185 6.9311166109 0.7603733573
O 23.0438129644 6.4358253659 1.5992513709 (Problem: calculation stopped and entire row is missing below)
3
i = 5, time = 3.000, E = 1234567
Mg 22.9985897185 6.9311166109 0.7603733573
O 23.0438129644 6.4358253659 1.5992513709
O 23.8223149199 7.2029442290 0.4030956770 sdffs (<--Problem: rarely, additional characters can be printed but I figured out how to identify the longest lines in the file and don't have this problem this time)
问题是计算可能会失败(然后需要重新启动),因为结果正在打印到文件中。这意味着当我尝试使用结果时,我遇到了问题。
我的问题是,我如何才能发现出现问题和结果文件被弄乱的情况?最常见的问题是没有“3”行结果(加上 header,这是 i = ... 所在的行)?如果我能找到问题行,我就可以删除那个时间步。
这是我在尝试使用 messed-up 文件时得到的错误输出示例:
Traceback (most recent call last):
File "/mtn/storage/software/languages/anaconda/Anaconda3-2018.12/lib/python3.7/site-packages/aser/io/extxyz.py", line 593, in read_xyz
nentss = int(line)
ValueError: invalid literal for int() with base 10: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "pythonPostProcessingCode.py", line 25, in <module>
path = read('%s%s' % (filename, fileext) , format='xyz', index=':') # <--This line tells me that Python cannot read in a particular time step because the formatting is messed up.
我对 scripting/Awk 等没有经验,所以如果有人认为我没有使用适当的问题标签,欢迎 heads-up。谢谢。
将记录的匹配分布在 2 行中以尝试合并 i = ...
有点困难,但我认为您实际上不需要这样做。看起来一条新记录可以通过出现只有一列的行来区分。如果是这种情况,您可以执行以下操作:
awk -v c=330 -v p=1 'function pr(n) {
if( n - p == c) printf "%s", buf;
buf = ""}
NF == 1 { pr(NR); p = NR; c = }
{buf = sprintf("%s%s\n", buf, [=10=])}
END {pr(NR+1)}' input-file
在上面,每当看到一行只有一条记录时,期望的是下一条记录中会有很多行。如果该号码不匹配,则不打印记录。为避免该逻辑,只需删除第 4 行末尾附近的 c =
。您需要 -v c=330
的唯一原因是启用该分配的删除;如果要单列行作为记录的行数,可以省略-v c=330
.
header 加上 330 表示 331 行文本等
awk 'BEGIN { RS="i =" } { split([=10=],bits,"\n");if (length(bits)-1==331) { print RS[=10=] } }' file > newfile
解释:
awk 'BEGIN {
RS="i ="
}
{
split([=11=],bits,"\n");
if (length(bits)-1==331) {
print RS[=11=]
}
}' file > newfile
在处理名为 file 的文件中的任何行之前,将记录分隔符设置为“i =”。然后,对于每条记录,使用 split 将记录 ($0) 拆分成一个数组 bits,以换行为分隔符。其中数组位的长度,减 1 是 331 打印记录分隔符加上记录,将输出重定向到一个名为 newfile
的新文件
如果允许写入中间文件,您可以使用csplit
在单独的文件中抓取每条记录。
csplit -k infile '/i = /' {*}
然后可以看到哪些记录是完整的,哪些没有使用wc -l xx*
(注意:xx
是分割文件的默认前缀)
然后您可以对这些记录做任何您想做的事情,包括恰好有 331 行的列表文件:
wc -l xx* | sed -n 's/^ *331 \(xx.*\)//p'
如果您想用所有 有效 记录构建一个新文件,只需将它们连接起来:
wc -l xx* | sed -n 's/^ *331 \(xx.*\)//p' | xargs cat > newfile
除其他用途外,您还可以存档失败的记录:
wc -l xx* | sed -e '$d' -e '/^ *331 \(xx.*\)/d' | xargs cat > failures
听起来这就是你想要的:
$ cat tst.awk
/^ i =/ {
prt()
expNumLines = prev + 1
actNumLines = 2
rec = prev RS [=10=]
next
}
NF == 4 {
rec = rec RS [=10=]
actNumLines++
}
{ prev = [=10=] }
END { prt() }
function prt() {
if ( (actNumLines == expNumLines) && (rec != "") ) {
print "-------------"
print rec
}
}
$ awk -f tst.awk file
-------------
3
i = 3, time = 2.000, E = 1234567
Mg 22.9985897185 6.9311166109 0.7603733573
O 23.0438129644 6.4358253659 1.5992513709
-------------
3
i = 5, time = 3.000, E = 1234567
Mg 22.9985897185 6.9311166109 0.7603733573
O 23.0438129644 6.4358253659 1.5992513709
只需更改 prt()
函数即可对有效记录执行您想执行的任何操作。
这个答案实际上与 bash 无关,但如果性能是个问题,您可能会感兴趣,因为您似乎要处理非常大的文件。
考虑到您可以编译一些非常基本的 C 程序,您可以构建此代码:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Constants are hardcoded to make the program more readable
// But they could be passed as program argument
const char separator[]="i =";
const unsigned int requiredlines=331;
int main(void) {
char* buffer[331] = { NULL, };
ssize_t buffersizes[331] = { 0, };
size_t n = requiredlines+1; // Ignore lines until the separator is found
char* line = NULL;
size_t len = 0;
ssize_t nbread;
size_t i;
// Iterate through all lines
while ((nbread = getline(&line, &len, stdin)) != -1) {
// If the separator is found:
// - print the record (if valid)
// - reset the record (always)
if (strstr(line, separator)) {
if (n == requiredlines) {
for (i = 0 ; i < requiredlines ; ++i) printf("%s", buffer[i]);
}
n = 0;
}
// Add the line to the buffer, unless too many lines have been read
// (in which case we may discard lines until the separator is found again)
if (n < requiredlines) {
if (buffersizes[n] > nbread) {
strncpy(buffer[n], line, nbread);
buffer[n][nbread] = '[=10=]';
} else {
free(buffer[n]);
buffer[n] = line;
buffersizes[n] = nbread+1;
line = NULL;
len = 0;
}
}
++n;
}
// Don't forget about the last record, if valid
if (n == requiredlines) {
for (i = 0 ; i < requiredlines ; ++i) printf("%s", buffer[i]);
}
free(line);
for (i = 0 ; i < requiredlines ; ++i) free(buffer[i]);
return 0;
}
程序可以这样编译:
gcc -c prog.c && gcc -o prog prog.o
那么可以这样执行:
./prog < infile > outfile
为了简化代码,它从 stdin 读取并输出到 stdout,但是在 Bash 考虑到您可以使用的重定向流的所有选项,这已经足够了。如果需要,代码可以直接适应 read/write from/to 个文件。
我已经在一个包含 1000 万行的生成文件上对其进行了测试,并将其与基于 awk 的解决方案进行了比较。
(time awk 'BEGIN { RS="i =" } { split([=13=],bits,"\n");if (length(bits)-1==331) { printf "%s",RS[=13=] } }' infile) > outfile
real 0m24.655s
user 0m24.357s
sys 0m0.279s
(time ./prog < infile) > outfile
real 0m1.414s
user 0m1.291s
sys 0m0.121s
在此示例中,它的运行速度比 awk 解决方案快大约 18 倍。你的里程可能会有所不同(不同的数据,不同的硬件)但我想它应该总是快得多。
我应该提到 awk 解决方案的速度非常快(对于脚本解决方案而言)。我首先尝试用 C++ 编写解决方案,它的性能与 awk 相似,有时甚至比 awk 慢。
我有很大的文件,每个文件都存储了非常长的计算结果。这是一个文件示例,其中有五个时间步长的结果; 第三、第四和第五 个时间步的输出存在问题。
(请注意,我一直很懒,在我的示例中使用了相同的数字来表示每个时间步的结果。实际上,每个时间步的数字都是唯一的。)
3
i = 1, time = 1.000, E = 1234567
Mg 22.9985897185 6.9311166109 0.7603733573
O 23.0438129644 6.4358253659 1.5992513709
O 23.8223149199 7.2029442290 0.4030956770
3
i = 2, time = 1.500, E = 1234567
Mg 22.9985897185 6.9311166109 0.7603733573
O 23.0438129644 6.4358253659 1.5992513709
O 23.8223149199 7.2029442290 0.4030956770
3
i = 3, time = 2.000, E = 1234567
Mg 22.9985897185 6.9311166109 0.7603733573
O 23.0438129644 6.4358253659 1.5992513709
O 23.8223149199 (<--Problem: calculation stopped and some numbers are missing)
3
i = 4, time = 2.500, E = 1234567
Mg 22.9985897185 6.9311166109 0.7603733573
O 23.0438129644 6.4358253659 1.5992513709 (Problem: calculation stopped and entire row is missing below)
3
i = 5, time = 3.000, E = 1234567
Mg 22.9985897185 6.9311166109 0.7603733573
O 23.0438129644 6.4358253659 1.5992513709
O 23.8223149199 7.2029442290 0.4030956770 sdffs (<--Problem: rarely, additional characters can be printed but I figured out how to identify the longest lines in the file and don't have this problem this time)
问题是计算可能会失败(然后需要重新启动),因为结果正在打印到文件中。这意味着当我尝试使用结果时,我遇到了问题。
我的问题是,我如何才能发现出现问题和结果文件被弄乱的情况?最常见的问题是没有“3”行结果(加上 header,这是 i = ... 所在的行)?如果我能找到问题行,我就可以删除那个时间步。
这是我在尝试使用 messed-up 文件时得到的错误输出示例:
Traceback (most recent call last):
File "/mtn/storage/software/languages/anaconda/Anaconda3-2018.12/lib/python3.7/site-packages/aser/io/extxyz.py", line 593, in read_xyz
nentss = int(line)
ValueError: invalid literal for int() with base 10: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "pythonPostProcessingCode.py", line 25, in <module>
path = read('%s%s' % (filename, fileext) , format='xyz', index=':') # <--This line tells me that Python cannot read in a particular time step because the formatting is messed up.
我对 scripting/Awk 等没有经验,所以如果有人认为我没有使用适当的问题标签,欢迎 heads-up。谢谢。
将记录的匹配分布在 2 行中以尝试合并 i = ...
有点困难,但我认为您实际上不需要这样做。看起来一条新记录可以通过出现只有一列的行来区分。如果是这种情况,您可以执行以下操作:
awk -v c=330 -v p=1 'function pr(n) {
if( n - p == c) printf "%s", buf;
buf = ""}
NF == 1 { pr(NR); p = NR; c = }
{buf = sprintf("%s%s\n", buf, [=10=])}
END {pr(NR+1)}' input-file
在上面,每当看到一行只有一条记录时,期望的是下一条记录中会有很多行。如果该号码不匹配,则不打印记录。为避免该逻辑,只需删除第 4 行末尾附近的 c =
。您需要 -v c=330
的唯一原因是启用该分配的删除;如果要单列行作为记录的行数,可以省略-v c=330
.
header 加上 330 表示 331 行文本等
awk 'BEGIN { RS="i =" } { split([=10=],bits,"\n");if (length(bits)-1==331) { print RS[=10=] } }' file > newfile
解释:
awk 'BEGIN {
RS="i ="
}
{
split([=11=],bits,"\n");
if (length(bits)-1==331) {
print RS[=11=]
}
}' file > newfile
在处理名为 file 的文件中的任何行之前,将记录分隔符设置为“i =”。然后,对于每条记录,使用 split 将记录 ($0) 拆分成一个数组 bits,以换行为分隔符。其中数组位的长度,减 1 是 331 打印记录分隔符加上记录,将输出重定向到一个名为 newfile
的新文件如果允许写入中间文件,您可以使用csplit
在单独的文件中抓取每条记录。
csplit -k infile '/i = /' {*}
然后可以看到哪些记录是完整的,哪些没有使用wc -l xx*
(注意:xx
是分割文件的默认前缀)
然后您可以对这些记录做任何您想做的事情,包括恰好有 331 行的列表文件:
wc -l xx* | sed -n 's/^ *331 \(xx.*\)//p'
如果您想用所有 有效 记录构建一个新文件,只需将它们连接起来:
wc -l xx* | sed -n 's/^ *331 \(xx.*\)//p' | xargs cat > newfile
除其他用途外,您还可以存档失败的记录:
wc -l xx* | sed -e '$d' -e '/^ *331 \(xx.*\)/d' | xargs cat > failures
听起来这就是你想要的:
$ cat tst.awk
/^ i =/ {
prt()
expNumLines = prev + 1
actNumLines = 2
rec = prev RS [=10=]
next
}
NF == 4 {
rec = rec RS [=10=]
actNumLines++
}
{ prev = [=10=] }
END { prt() }
function prt() {
if ( (actNumLines == expNumLines) && (rec != "") ) {
print "-------------"
print rec
}
}
$ awk -f tst.awk file
-------------
3
i = 3, time = 2.000, E = 1234567
Mg 22.9985897185 6.9311166109 0.7603733573
O 23.0438129644 6.4358253659 1.5992513709
-------------
3
i = 5, time = 3.000, E = 1234567
Mg 22.9985897185 6.9311166109 0.7603733573
O 23.0438129644 6.4358253659 1.5992513709
只需更改 prt()
函数即可对有效记录执行您想执行的任何操作。
这个答案实际上与 bash 无关,但如果性能是个问题,您可能会感兴趣,因为您似乎要处理非常大的文件。
考虑到您可以编译一些非常基本的 C 程序,您可以构建此代码:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Constants are hardcoded to make the program more readable
// But they could be passed as program argument
const char separator[]="i =";
const unsigned int requiredlines=331;
int main(void) {
char* buffer[331] = { NULL, };
ssize_t buffersizes[331] = { 0, };
size_t n = requiredlines+1; // Ignore lines until the separator is found
char* line = NULL;
size_t len = 0;
ssize_t nbread;
size_t i;
// Iterate through all lines
while ((nbread = getline(&line, &len, stdin)) != -1) {
// If the separator is found:
// - print the record (if valid)
// - reset the record (always)
if (strstr(line, separator)) {
if (n == requiredlines) {
for (i = 0 ; i < requiredlines ; ++i) printf("%s", buffer[i]);
}
n = 0;
}
// Add the line to the buffer, unless too many lines have been read
// (in which case we may discard lines until the separator is found again)
if (n < requiredlines) {
if (buffersizes[n] > nbread) {
strncpy(buffer[n], line, nbread);
buffer[n][nbread] = '[=10=]';
} else {
free(buffer[n]);
buffer[n] = line;
buffersizes[n] = nbread+1;
line = NULL;
len = 0;
}
}
++n;
}
// Don't forget about the last record, if valid
if (n == requiredlines) {
for (i = 0 ; i < requiredlines ; ++i) printf("%s", buffer[i]);
}
free(line);
for (i = 0 ; i < requiredlines ; ++i) free(buffer[i]);
return 0;
}
程序可以这样编译:
gcc -c prog.c && gcc -o prog prog.o
那么可以这样执行:
./prog < infile > outfile
为了简化代码,它从 stdin 读取并输出到 stdout,但是在 Bash 考虑到您可以使用的重定向流的所有选项,这已经足够了。如果需要,代码可以直接适应 read/write from/to 个文件。
我已经在一个包含 1000 万行的生成文件上对其进行了测试,并将其与基于 awk 的解决方案进行了比较。
(time awk 'BEGIN { RS="i =" } { split([=13=],bits,"\n");if (length(bits)-1==331) { printf "%s",RS[=13=] } }' infile) > outfile
real 0m24.655s
user 0m24.357s
sys 0m0.279s
(time ./prog < infile) > outfile
real 0m1.414s
user 0m1.291s
sys 0m0.121s
在此示例中,它的运行速度比 awk 解决方案快大约 18 倍。你的里程可能会有所不同(不同的数据,不同的硬件)但我想它应该总是快得多。
我应该提到 awk 解决方案的速度非常快(对于脚本解决方案而言)。我首先尝试用 C++ 编写解决方案,它的性能与 awk 相似,有时甚至比 awk 慢。