【问题标题】:searching for a match then replacing a value after this match with a value from the previous line搜索匹配项,然后将此匹配项之后的值替换为上一行中的值
【发布时间】:2019-10-05 21:53:20
【问题描述】:

我有多个文件,其中包含数千行以下模式:

O   HOH     1      11.700 -11.906   1.533
H   HOH     0      12.561 -12.318   1.596
H   HOH     0      11.445 -12.031   0.627
O   HOH     2      17.897   8.999  -0.104
H   HOH     0      18.444   8.521   0.516
H   HOH     0      17.127   8.457  -0.223
O   HOH     3      -0.688  10.310 -15.189
H   HOH     0      -0.760   9.366 -15.055
H   HOH     0      -0.494  10.665 -14.324
O   HOH     4     -10.418   3.733   4.069
H   HOH     0      -9.928   3.726   4.897
H   HOH     0     -11.222   4.218   4.268
O   HOH     5       2.655  -8.910  13.338
H   HOH     0       2.377  -9.739  13.737
H   HOH     0       1.847  -8.531  12.992
O   HOH     6      -0.191  -1.010 -19.469
H   HOH     0       0.455  -1.460 -18.924
H   HOH     0      -0.571  -0.343 -18.895
O   HOH     7      14.338 -15.985  -1.467
H   HOH     0      15.129 -15.647  -1.886
H   HOH     0      13.854 -16.408  -2.175
O   HOH     8     -15.510  -7.073   1.857
H   HOH     0     -15.526  -6.116   1.874
H   HOH     0     -16.341  -7.321   1.464
O   HOH     9       9.155   6.561  -7.729
H   HOH     0       9.419   7.335  -7.245
H   HOH     0       9.943   6.018  -7.768
.
.
.
.
O   HOH  999999     9.155   6.561  -7.729
H   HOH     0       9.419   7.335  -7.245
H   HOH     0       9.943   6.018  -7.768

我需要用'H HOH xxx' 替换'H HOH 0' 的每两个连续瞬间,其中xxx 是0s 正上方的值

我尝试过的是,我使用

将大文件拆分为 3 行文件
split -l 3 foo

然后使用 awk 我将所需的值复制到另一个文件中

awk 'NR==1 print $3' > foo--

然后尝试使用 sed 通过读取文件 foo-- 并替换 foo 中的“0”来替换模式

sed -e '/  0  /{r foo--' -e 'd}' foo 

但这没有用

很确定有更好的方法来做到这一点

最终结果应该是这样的:

O   HOH     1      11.700 -11.906   1.533
H   HOH     1      12.561 -12.318   1.596
H   HOH     1      11.445 -12.031   0.627
O   HOH     2      17.897   8.999  -0.104
H   HOH     2      18.444   8.521   0.516
H   HOH     2      17.127   8.457  -0.223
O   HOH     3      -0.688  10.310 -15.189
H   HOH     3      -0.760   9.366 -15.055
H   HOH     3      -0.494  10.665 -14.324
O   HOH     4     -10.418   3.733   4.069
H   HOH     4      -9.928   3.726   4.897
H   HOH     4     -11.222   4.218   4.268
O   HOH     5       2.655  -8.910  13.338
H   HOH     5       2.377  -9.739  13.737
H   HOH     5       1.847  -8.531  12.992
O   HOH     6      -0.191  -1.010 -19.469
H   HOH     6       0.455  -1.460 -18.924
H   HOH     6      -0.571  -0.343 -18.895
.
.
.
O   HOH     99999      -0.191  -1.010 -19.469
H   HOH     99999       0.455  -1.460 -18.924
H   HOH     99999      -0.571  -0.343 -18.895

【问题讨论】:

  • H HOH 0 是否总是连续出现在两行中?
  • @Cyrus.. 是的
  • 如果连续出现4次会怎样?
  • @Graeme。这些实际上是水分子的笛卡尔坐标。水分子的分子式为 H2O。所以在这种情况下,总是有一个 O 和 2 个连续的 H 原子。不会连续出现 4 个“H HOH 0”实例。

标签: bash awk sed


【解决方案1】:

您可以在awk 中做您需要的事情,但要保持格式整洁,需要使用FIELDWIDTHSfprinf。问题是由于awk 在必须重新计算记录时(例如更改记录时)剥离了前导和尾随 空格。未更改的记录不会以这种方式修改。这在提供表格输出时会带来挑战。

要使用每个字段的FIELDWIDTHS,请在BEGIN 规则中提供宽度列表。示例:

awk '
    BEGIN  { FIELDWIDTHS = "1 6 8 10 8 8" }
    $1 == "O" {n = $3}
    $1 == "H" {$3 = n}
    {printf "%s%6s%8s%10s%8s%8s\n", $1,$2,$3,$4,$5,$6}
' file

注意: FIELDWIDTHS 是 GNU awk 功能)

使用您的数据,删除...,您可以在命令行中复制/鼠标中键粘贴进行测试,例如

$ awk '
>     BEGIN  { FIELDWIDTHS = "1 6 8 10 8 8" }
>     $1 == "O" {n = $3}
>     $1 == "H" {$3 = n}
>     {printf "%s%6s%8s%10s%8s%8s\n", $1,$2,$3,$4,$5,$6}
> ' file
O   HOH     1      11.700 -11.906   1.533
H   HOH     1      12.561 -12.318   1.596
H   HOH     1      11.445 -12.031   0.627
O   HOH     2      17.897   8.999  -0.104
H   HOH     2      18.444   8.521   0.516
H   HOH     2      17.127   8.457  -0.223
O   HOH     3      -0.688  10.310 -15.189
H   HOH     3      -0.760   9.366 -15.055
H   HOH     3      -0.494  10.665 -14.324
O   HOH     4     -10.418   3.733   4.069
H   HOH     4      -9.928   3.726   4.897
H   HOH     4     -11.222   4.218   4.268
O   HOH     5       2.655  -8.910  13.338
H   HOH     5       2.377  -9.739  13.737
H   HOH     5       1.847  -8.531  12.992
O   HOH     6      -0.191  -1.010 -19.469
H   HOH     6       0.455  -1.460 -18.924
H   HOH     6      -0.571  -0.343 -18.895
O   HOH     7      14.338 -15.985  -1.467
H   HOH     7      15.129 -15.647  -1.886
H   HOH     7      13.854 -16.408  -2.175
O   HOH     8     -15.510  -7.073   1.857
H   HOH     8     -15.526  -6.116   1.874
H   HOH     8     -16.341  -7.321   1.464
O   HOH     9       9.155   6.561  -7.729
H   HOH     9       9.419   7.335  -7.245
H   HOH     9       9.943   6.018  -7.768
O   HOH  999999     9.155   6.561  -7.729
H   HOH  999999     9.419   7.335  -7.245
H   HOH  999999     9.943   6.018  -7.768

【讨论】:

    【解决方案2】:

    如果您不关心字段之间的间距,只需获取所需的值:

    $ awk '$1=="H"{$3=p} {p=$3} 1' file
    O   HOH     1      11.700 -11.906   1.533
    H HOH 1 12.561 -12.318 1.596
    H HOH 1 11.445 -12.031 0.627
    O   HOH     2      17.897   8.999  -0.104
    H HOH 2 18.444 8.521 0.516
    H HOH 2 17.127 8.457 -0.223
    O   HOH     3      -0.688  10.310 -15.189
    H HOH 3 -0.760 9.366 -15.055
    H HOH 3 -0.494 10.665 -14.324
    O   HOH     4     -10.418   3.733   4.069
    H HOH 4 -9.928 3.726 4.897
    H HOH 4 -11.222 4.218 4.268
    O   HOH     5       2.655  -8.910  13.338
    H HOH 5 2.377 -9.739 13.737
    H HOH 5 1.847 -8.531 12.992
    O   HOH     6      -0.191  -1.010 -19.469
    H HOH 6 0.455 -1.460 -18.924
    H HOH 6 -0.571 -0.343 -18.895
    O   HOH     7      14.338 -15.985  -1.467
    H HOH 7 15.129 -15.647 -1.886
    H HOH 7 13.854 -16.408 -2.175
    O   HOH     8     -15.510  -7.073   1.857
    H HOH 8 -15.526 -6.116 1.874
    H HOH 8 -16.341 -7.321 1.464
    O   HOH     9       9.155   6.561  -7.729
    H HOH 9 9.419 7.335 -7.245
    H HOH 9 9.943 6.018 -7.768
    O   HOH  999999     9.155   6.561  -7.729
    H HOH 999999 9.419 7.335 -7.245
    H HOH 999999 9.943 6.018 -7.768
    

    或以空格分隔,最后 4 个字段右对齐:

    $ awk '$1=="H"{$3=p} {p=$3} 1' file | column -t -R3,4,5,6
    O  HOH       1   11.700  -11.906    1.533
    H  HOH       1   12.561  -12.318    1.596
    H  HOH       1   11.445  -12.031    0.627
    O  HOH       2   17.897    8.999   -0.104
    H  HOH       2   18.444    8.521    0.516
    H  HOH       2   17.127    8.457   -0.223
    O  HOH       3   -0.688   10.310  -15.189
    H  HOH       3   -0.760    9.366  -15.055
    H  HOH       3   -0.494   10.665  -14.324
    O  HOH       4  -10.418    3.733    4.069
    H  HOH       4   -9.928    3.726    4.897
    H  HOH       4  -11.222    4.218    4.268
    O  HOH       5    2.655   -8.910   13.338
    H  HOH       5    2.377   -9.739   13.737
    H  HOH       5    1.847   -8.531   12.992
    O  HOH       6   -0.191   -1.010  -19.469
    H  HOH       6    0.455   -1.460  -18.924
    H  HOH       6   -0.571   -0.343  -18.895
    O  HOH       7   14.338  -15.985   -1.467
    H  HOH       7   15.129  -15.647   -1.886
    H  HOH       7   13.854  -16.408   -2.175
    O  HOH       8  -15.510   -7.073    1.857
    H  HOH       8  -15.526   -6.116    1.874
    H  HOH       8  -16.341   -7.321    1.464
    O  HOH       9    9.155    6.561   -7.729
    H  HOH       9    9.419    7.335   -7.245
    H  HOH       9    9.943    6.018   -7.768
    O  HOH  999999    9.155    6.561   -7.729
    H  HOH  999999    9.419    7.335   -7.245
    H  HOH  999999    9.943    6.018   -7.768
    

    或 TSV 格式:

    $ awk -v OFS='\t' '{$3=($1=="H" ? p : $3); p=$3} 1' file
    O       HOH     1       11.700  -11.906 1.533
    H       HOH     1       12.561  -12.318 1.596
    H       HOH     1       11.445  -12.031 0.627
    O       HOH     2       17.897  8.999   -0.104
    H       HOH     2       18.444  8.521   0.516
    H       HOH     2       17.127  8.457   -0.223
    O       HOH     3       -0.688  10.310  -15.189
    H       HOH     3       -0.760  9.366   -15.055
    H       HOH     3       -0.494  10.665  -14.324
    O       HOH     4       -10.418 3.733   4.069
    H       HOH     4       -9.928  3.726   4.897
    H       HOH     4       -11.222 4.218   4.268
    O       HOH     5       2.655   -8.910  13.338
    H       HOH     5       2.377   -9.739  13.737
    H       HOH     5       1.847   -8.531  12.992
    O       HOH     6       -0.191  -1.010  -19.469
    H       HOH     6       0.455   -1.460  -18.924
    H       HOH     6       -0.571  -0.343  -18.895
    O       HOH     7       14.338  -15.985 -1.467
    H       HOH     7       15.129  -15.647 -1.886
    H       HOH     7       13.854  -16.408 -2.175
    O       HOH     8       -15.510 -7.073  1.857
    H       HOH     8       -15.526 -6.116  1.874
    H       HOH     8       -16.341 -7.321  1.464
    O       HOH     9       9.155   6.561   -7.729
    H       HOH     9       9.419   7.335   -7.245
    H       HOH     9       9.943   6.018   -7.768
    O       HOH     999999  9.155   6.561   -7.729
    H       HOH     999999  9.419   7.335   -7.245
    H       HOH     999999  9.943   6.018   -7.768
    

    但是,如果您需要保留原始间距,那么使用 GNU awk 将第 4 个 arg 用于 split() 这将保留您开始使用的任何间距:

    $ cat tst.awk
    {
        if ($1 == "H") {
            split(prev,pflds,FS,pseps)
            n = split($0,flds,FS,seps)
            seps[2] = pseps[2]
            flds[3] = pflds[3]
            seps[3] = sprintf("%*s",length(pseps[3]) + length(pflds[4]) - length(flds[4]),"")
            $0 = ""
            for (i=1; i<=n; i++) {
                $0 = $0 flds[i] seps[i]
            }
        }
        print
        prev = $0
    }
    

    .

    $ awk -f tst.awk file
    O   HOH     1      11.700 -11.906   1.533
    H   HOH     1      12.561 -12.318   1.596
    H   HOH     1      11.445 -12.031   0.627
    O   HOH     2      17.897   8.999  -0.104
    H   HOH     2      18.444   8.521   0.516
    H   HOH     2      17.127   8.457  -0.223
    O   HOH     3      -0.688  10.310 -15.189
    H   HOH     3      -0.760   9.366 -15.055
    H   HOH     3      -0.494  10.665 -14.324
    O   HOH     4     -10.418   3.733   4.069
    H   HOH     4      -9.928   3.726   4.897
    H   HOH     4     -11.222   4.218   4.268
    O   HOH     5       2.655  -8.910  13.338
    H   HOH     5       2.377  -9.739  13.737
    H   HOH     5       1.847  -8.531  12.992
    O   HOH     6      -0.191  -1.010 -19.469
    H   HOH     6       0.455  -1.460 -18.924
    H   HOH     6      -0.571  -0.343 -18.895
    O   HOH     7      14.338 -15.985  -1.467
    H   HOH     7      15.129 -15.647  -1.886
    H   HOH     7      13.854 -16.408  -2.175
    O   HOH     8     -15.510  -7.073   1.857
    H   HOH     8     -15.526  -6.116   1.874
    H   HOH     8     -16.341  -7.321   1.464
    O   HOH     9       9.155   6.561  -7.729
    H   HOH     9       9.419   7.335  -7.245
    H   HOH     9       9.943   6.018  -7.768
    O   HOH  999999     9.155   6.561  -7.729
    H   HOH  999999     9.419   7.335  -7.245
    H   HOH  999999     9.943   6.018  -7.768
    

    【讨论】:

    • 对于涉及column的那个,我得到column: invalid option -- 'R'
    • 好吧,猜猜它需要 GNU 列来右对齐,你一定没有它。
    【解决方案3】:

    使用 GNU awk:

    awk '$1=="O"{p=$3}; $1=="H" {$3=p} {printf("%s %5s      %s %10.3f %7.3f %7.3f\n",$1,$2,$3,$4,$5,$6)} ' file
    

    输出:

    OH 1 11.700 -11.906 1.533 H HOH 1 12.561 -12.318 1.596 H HOH 1 11.445 -12.031 0.627 O HOH 2 17.897 8.999 -0.104 H HOH 2 18.444 8.521 0.516 H HOH 2 17.127 8.457 -0.223 OH 3 -0.688 10.310 -15.189 H HOH 3 -0.760 9.366 -15.055 H HOH 3 -0.494 10.665 -14.324 OH 4 -10.418 3.733 4.069 H HOH 4 -9.928 3.726 4.897 H HOH 4 -11.222 4.218 4.268 OH 5 2.655 -8.910 13.338 H HOH 5 2.377 -9.739 13.737 H HOH 5 1.847 -8.531 12.992 OH 6 -0.191 -1.010 -19.469 H HOH 6 0.455 -1.460 -18.924 H HOH 6 -0.571 -0.343 -18.895 OH 7 14.338 -15.985 -1.467 H HOH 7 15.129 -15.647 -1.886 . . . OH 999999 9.155 6.561 -7.729 H HOH 999999 9.419 7.335 -7.245 H HOH 999999 9.943 6.018 -7.768

    【讨论】:

    • 这在任何 awk 中的行为都是一样的,其中没有任何 GNU-only。
    【解决方案4】:

    这应该可行:

    awk '{ if($1=="H" && $2=="HOH" && $3=="0") $3=val; else val=$3 } 1'
    

    或者,保留字段宽度:

    awk '{
        if($1=="H" && $2=="HOH" && $3=="0")
            sub(sprintf("0 {1,%d}", length(val)), val " ")
        else
            val=$3
    } 1'
    

    【讨论】:

    • 它适用于值,但输出格式被打乱。第二种方法要好得多,但 999999 记录是偏移的。 (由于输入文件中的格式不一致)我喜欢你对sub 的想法。
    • @David,是的!使用 awk 保留原始字段宽度很困难。
    • 咯咯笑——我花了将近一个小时才发现解决方案:)
    【解决方案5】:

    这可能对你有用(GNU sed):

    sed -E '/^H\s+HOH\s+0\s/!{h;b};N;/\nH\s+HOH\s+0\s/{G;s/0(.*\n.[^0]*)0(.*)\n\S\s+\S+\s+(\S+).*/\3\1\3\2/};P;D' file
    

    如果一行不是以H HOH 0 开头,则正常打印并在保留空间中保留一份副本。否则,附加以下行,如果它也以H HOH 0 开头,则从保留空间附加副本并使用替换将前两行重新排列为所需的格式。在所有其他情况下,打印下一行并重复。

    根据要求更详细:

    • /^H\s+HOH\s+0\s/!{h;b} 如果一行没有开始 H HOH 0,则正常打印并在保留空间中保留一份副本(h 复制到保留空间,b 跳出 sed 命令循环,将当前行打印到模式空间,如果可用,获取下一行)。
    • N 当前行开始 H HOH 0 追加下一行。
    • /\nH\s+HOH\s+0\s/ 附加行也以H HOH 0 开头,所以:
    • G 附加保留空间的副本,即没有以H HOH 0 开头的最后一行,并且:
    • s/0(.*\n.[^0]*)0(.*)\n\S\s+\S+\s+(\S+).*/\3\1\3\2/ 用保留空间副本中的第 3 个字段替换最后两行中的第 3 个字段。
    • P 打印到第一个换行符(包括第一个换行符)。
    • D 删除直到并包括第一个换行符。

    最后两个 sed 命令适用于附加到模式空间的行与第二个的前三个字段不匹配的情况。这允许这些行中的第一行正常打印,而第二行则被视为该行已重新呈现。

    【讨论】:

    • 我真的很喜欢sed 解决方案。你能详细解释一下吗?
    • 这很完美。 sed 的好处是我不用担心awk 产生的列间距。我正在将接受的答案更改为这个答案。
    • I didn't have to worry about the column spacings produced by awk - 您是否查看了来自 sed 命令的 999999 行的输出间距,并考虑了它与其他多位第三列值的行为?如果您不排列所有输出列,您也不必担心 awk 产生的列间距。如果/当您想在将来做一些稍微不同的事情时,您甚至不担心您可能无法更新此 sed 脚本吗?
    • @EdMorton。您实际上是对的,我必须至少再执行 2 个 sed 命令来修复较大数字产生的间距。我猜awk 更适合这种情况。
    • 是的,如果/当您的需求发生变化并且我发布的 awk 脚本至少可以在每个 UNIX 机器上的任何 shell 中使用任何 awk 时,您实际上可以阅读它并对其进行简单的修改。跨度>
    猜你喜欢
    • 2020-08-04
    • 2018-02-26
    • 1970-01-01
    • 2014-08-15
    • 1970-01-01
    • 2013-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多