【问题标题】:Column replacement with awk, with retaining the format用 awk 替换列,保留格式
【发布时间】:2014-04-07 19:47:24
【问题描述】:

这是文件a.pdb

ATOM      1  N   ARG     1       0.000   0.000   0.000  1.00  0.00           N
ATOM      2  H1  ARG     1       0.000   0.000   0.000  1.00  0.00           H
ATOM      3  H2  ARG     1       0.000   0.000   0.000  1.00  0.00           H
ATOM      4  H3  ARG     1       0.000   0.000   0.000  1.00  0.00           H

这是文件a.xyz

16.388 -5.760 -23.332
17.226 -5.608 -23.768
15.760 -5.238 -23.831
17.921 -5.926 -26.697

我想用a.xyz 列替换a.pdb 的第6,7 和第8 列。替换后,我需要维护a.pdb 的制表符/空格/列。

我试过了:

awk 'NR==FNR {fld1[NR]=$1; fld2[NR]=$2; fld3[NR]=$3; next} {$6=fld1[FNR]; $7=fld2[FNR]; $8=fld3[FNR]}1' a.xyz a.pdb 

但它不保留格式。

【问题讨论】:

  • 替换算法是什么?一个文件的第一行内容与另一个文件的第一行相同?
  • 行号匹配。 @fedorqui
  • 好吧,我猜a.pdb 是制表符分隔的,而a.xyz 是空格分隔的。如果您将它们都设为 tab sep 会怎样?
  • 我试过 cat a.xyz | column -t 选项,但在 awk 替换后 a.pdb 失去其格式。它不会保持列号对齐。
  • BEGIN{FS=OFS="\t"} 让你的FS 和OFS 显式?祝你好运。

标签: replace awk format


【解决方案1】:

这正是 GNU awk 中用于 split() 的第四个参数的发明目的:

gawk '
NR==FNR { pdb[NR]=$0; next }
{
    split(pdb[FNR],flds,FS,seps)
    flds[6]=$1
    flds[7]=$2
    flds[8]=$3
    for (i=1;i in flds;i++)
        printf "%s%s", flds[i], seps[i]
    print ""
}
' a.pdb a.xyz

ATOM      1  N   ARG     1       16.388   -5.760   -23.332  1.00  0.00           N
ATOM      2  H1  ARG     1       17.226   -5.608   -23.768  1.00  0.00           H
ATOM      3  H2  ARG     1       15.760   -5.238   -23.831  1.00  0.00           H
ATOM      4  H3  ARG     1       17.921   -5.926   -26.697  1.00  0.00           H

【讨论】:

  • 做得很好。我想对一个问题:) 投赞成票。在for loop 中,您选择使用i in flds 而不是捕获split 函数返回值并执行i<=n。这样做有什么好处吗?
  • 无论哪种方式都没什么大不了的,它只是少了一个全局变量,您无需担心填充并可能与脚本中其他地方的同名变量发生冲突。在这么小的脚本中显然不是问题。请注意,这种方法仅适用于数组索引中没有空洞的情况。
  • 好的,知道了!感谢您的解释。 +1 正式给予!顺便说一句,我们非常需要在每个人的桌子上都有一本awk 食谱! 眨眼眨眼
  • 是的,我已经考虑过了。类似于 Chris Johnson 的“Bash Scripting Recipes”,但适用于 awk。总有一天……
  • +1 为 gawk 的出色功能,Ed。但小数点不在第 35、43 和 51 列。
【解决方案2】:

不是一个通用的解决方案,但这可能适用于这种特殊情况:

awk 'NR==FNR{for(i=6; i<=8; i++) A[FNR,i]=$(i-5); next} {for(i=6; i<=8; i++) sub($i,A[FNR,i])}1' file2 file1

awk '{for(i=6; i<=8; i++) if(NR==FNR) A[FNR,i]=$(i-5); else sub($i,A[FNR,i])} NR>FNR' file2 file1

不过,有一点变化。我们需要知道字段宽度以防止这种情况发生。

-- 或者可能带有子字符串:

awk 'NR==FNR{A[FNR]=$0; next} {print substr($0,1,p) FS A[FNR] substr($0,p+length(A[FNR]))}' p=33 file2 file1

--在 OP 的原始解决方案中更改它:

awk 'NR==FNR {fld1[NR]=$1; fld2[NR]=$2; fld3[NR]=$3; next} {sub($6,fld1[FNR]); sub($7,fld2[FNR]); sub($8,fld3[FNR])}1' file file1

与前 2 条建议的限制相同。

所以 1、2 和 4 使用 sub 替换,这不是防水解决方案,因为较早的字段可能会干扰并且它使用正则表达式而不是字符串(因此正则表达式点恰好与实际点匹配) ,但是有了这个特定的输入,它可能会成功..

大概nr。 3将是一个更简单的方法..

--编辑-- 我认为这适用于给定的输入:

awk 'NR==FNR{A[FNR]=$1 "  " $2 " " $3; next} {print substr($0,1,p) A[FNR] substr($0,p+length(A[FNR]))}' p=32  file2 file1

但我认为需要像printfsprint 这样的格式来使其万无一失。 所以,也许是这样的:

awk 'NR==FNR{A[FNR]=sprintf("%7.3f %7.3f %8.4f", $1, $2, $3); next} {print substr($0,1,p) A[FNR] substr($0,p+length(A[FNR]))}' p=31 file2 file1

或不在一行:

awk '
  NR==FNR {
    A[FNR]=sprintf("%7.3f %7.3f %8.4f", $1, $2, $3)
    next
  }
  {
    print substr($0,1,p) A[FNR] substr($0,p+length(A[FNR]))
  }
' p=31 file2 file1

【讨论】:

  • 注意 OPs 语法没问题,他只是询问保持格式。
  • @fedorqui,好点,我添加了对其他建议的改编。
  • 除了第 6-8 列的小变化外,它可以工作。 a.pdb 中的第 6-8 列左对齐。小数点应出现在第 35、43 和 51 列。
【解决方案3】:

你可以试试这个

paste -d' '  test4 test5 |awk '{print $1,$2,$3,$4,$5,$12,$13,$14,$9,$10,$11}'

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-04-24
    • 1970-01-01
    • 1970-01-01
    • 2014-11-06
    • 2018-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多