我有一个如下所示的文件:
# Time-averaged data for fix avetimeall
# TimeStep Number-of-rows
# Row c_gyrationchunkall
1000 3
1 2.09024e-14
2 4.88628
3 5.69321
2000 3
1 2.10518e-14
2 8.33702
3 8.83162
3000 3
1 1.96656e-14
2 12.1396
3 11.5835
...
在我的文件中,前三行始终是标题。在标题之后,我的文件列出了相同大小的数据块,每个数据块都以标签子标题开头。我想重新组织文件中的数据,以便将每个块中的数据发送到以该块标签的相关部分开始的行中,然后列出该块的相关数据值,所有数据都用空格分隔开。作为示例,我想将上面的示例转换为:
# Time-averaged data for fix avetimeall
# TimeStep c_gyrationchunkall
1000 2.09024e-14 4.88628 5.69321
2000 2.10518e-14 8.33702 8.83162
3000 1.96656e-14 12.1396 11.5835
...
我如何在 Bash 中执行此操作?我有一些 Bash 经验,但恐怕不足以快速处理这个问题......
答案1
使用任何 awk,无论3
块中的行数是否可以变化:
$ awk '
NR == 2 { $3=""; saved=$0; next }
NR == 3 { $0=saved $3 }
NR < 4 { print; next }
!numLines {
numLines = $2
printf "%s%s", $1, OFS
next
}
{ printf "%s%s", $2, (--numLines ? OFS : ORS) }
' file
# Time-averaged data for fix avetimeall
# TimeStep c_gyrationchunkall
1000 2.09024e-14 4.88628 5.69321
2000 2.10518e-14 8.33702 8.83162
3000 1.96656e-14 12.1396 11.5835
跟进下的讨论泽维尔·G 的回答关于可读性风格的偏好,这里有一个 awk 脚本,其编写风格与 shell 脚本相同(并包含在 shell 脚本中,因此它在外部的行为方式相同),但它的运行速度要快几个数量级*比 shell 脚本更健壮和可移植:
$ cat ./script_filename
#!/usr/bin/env bash
awk '
BEGIN {
# Reformat comments:
getline first_line
print first_line
getline; split($0,line2)
getline; split($0,line3)
printf "# %s %s\n", line2[2], line3[3]
# Reformat data:
while ( getline > 0 ) {
timestep=$1; number_of_rows=$2
printf "%s", timestep
for ( i=1; i<=number_of_rows; i++ ) {
getline; row_value=$NF
printf " %s", row_value
}
print ""
}
}
'
$ ./script_filename < input
# Time-averaged data for fix avetimeall
# TimeStep c_gyrationchunkall
1000 2.09024e-14 4.88628 5.69321
2000 2.10518e-14 8.33702 8.83162
3000 1.96656e-14 12.1396 11.5835
* 以下是在包含 90,000 条 OP 记录的文件上运行 bash 脚本与上述 awk 脚本的第三次运行计时结果:
$ time ./script_bash < file > /dev/null
real 0m9.425s
user 0m5.062s
sys 0m4.139s
$ time ./script_awk < file > /dev/null
real 0m0.265s
user 0m0.171s
sys 0m0.000s
答案2
使用乐(以前称为 Perl_6)
用于skip
暂时忘记标题行:
~$ raku -e 'my @a = lines.skip(3).rotor(4, partial => True).map: *.words; .[0,3,5,7].put for @a;' file
#OR
~$ raku -e 'my @a = lines.skip(3).batch(4).map: *.words; .[0,3,5,7].put for @a;' file
上面是用 Raku(Perl 编程语言家族的成员)编写的答案。简而言之,lines
读入、skip
ping 前 3 个标题行。每 4 行都一起rotor
编辑batch
,包括partial
文件末尾的最终“旋转”。当我们这样做时,让我们将每个rotor
/batch
分成空格分隔的words
。
这些转子/批次的 4 行每行都在空白处断开,保存在@
名为 的签名数组中@a
。最后(在第二个语句中),使用for
每个@a
位置进行迭代put
,并注意删除不需要的元素(通过索引括号[0,3,5,7]
)。
输入示例:
# Time-averaged data for fix avetimeall
# TimeStep Number-of-rows
# Row c_gyrationchunkall
1000 3
1 2.09024e-14
2 4.88628
3 5.69321
2000 3
1 2.10518e-14
2 8.33702
3 8.83162
3000 3
1 1.96656e-14
2 12.1396
3 11.5835
示例输出:
1000 2.09024e-14 4.88628 5.69321
2000 2.10518e-14 8.33702 8.83162
3000 1.96656e-14 12.1396 11.5835
put
关于标题行,用两个语句启动 Raku 代码可能很容易,例如put "Time-averaged data...";
等。但实际上,以下工作可以给出 OP 所需的输出:
~$ raku -e 'lines[0].put; .words[0..1, *-1].put for lines[0..1].rotor(2); \
my @a = lines.rotor(4, partial => True).map: *.words; \
.[0,3,5,7].put for @a;' file
## Time-averaged data for fix avetimeall
# TimeStep c_gyrationchunkall
1000 2.09024e-14 4.88628 5.69321
2000 2.10518e-14 8.33702 8.83162
3000 1.96656e-14 12.1396 11.5835
答案3
使用AWK:
$ awk '
NR==2{sub(/[[:space:]]+[^[:space:]]+$/,"");rec = $0; next}
NR==3{$0 = rec OFS $NF};
NR<4;
NR>3{printf "%s", (NR%4==0) ? ((NR==4) ? "" : ORS) $1 : ($1="")$0 }
END{if (NR)print ""}'
$ awk '
NR==2{sub(/[[:space:]]+[^[:space:]]+$/,"");rec = $0; next}
NR==3{$0 = rec OFS $NF};
NR<4;
$NF ~ /^[0-9]+$/{a=$NF;n=NR+a; sub(/[[:space:]]+[^[:space:]]+$/,""); printf "%s", $0; next}
NR<=n{$1 =""; printf "%s", $0((NR==n) ? ORS : "") }'
答案4
根据问题中提到的警告并使用示例输入作为文件 q762948,您可以通过简单的 awk 命令来执行此操作:
$ head -2 q762948 >result.txt
# dump the comments as required
$ tail +4 q762948 | awk '{c=(NR-1)%4} c==0{p=$1;print ""} c>0{p=$2}{printf p" "}'>>result.txt
$ cat result.txt
# Time-averaged data for fix avetimeall
# TimeStep Number-of-rows
1000 2.09024e-14 4.88628 5.69321
2000 2.10518e-14 8.33702 8.83162
3000 1.96656e-14 12.1396 11.5835