【问题标题】:Replacing a range with data from another file用另一个文件中的数据替换范围
【发布时间】:2016-09-10 03:51:14
【问题描述】:

我有一个包含以下文本的文件 (file1)-

1SER     CA    1   1.401   0.040   0.887
2GLN     CA    2   1.708  -0.155   1.002
3ALA     CA    3   1.870  -0.103   0.662
4GLU     CA    4   1.829   0.274   0.695

我有一个包含类似文本的单独文件 (file2)-

1MET     CA    1  17.704  15.987  17.370
2ARG     CA    2  17.811  16.145  17.712
3ARG     CA    3  17.634  16.267  18.034
4TYR     CA    4  17.465  16.615  18.002

我的目标是将file1中2-4范围内的字符替换为file2的2-4中的数据。

期望的输出-

1MET     CA    1   1.401   0.040   0.887
2ARG     CA    2   1.708  -0.155   1.002
3ARG     CA    3   1.870  -0.103   0.662
4TYR     CA    4   1.829   0.274   0.695

即file2 的 2-4 中的字符放在 file1 的 2-4 字节中。

我知道我可以使用cut -c 2-4 | sed ... 缩小所需区域的范围 但我无法从单独的文件中“读取”数据并替换到位。

我觉得在 awk 中可能会更容易,但请不要基于列的答案。需要根据文件中的字符范围(本例为2-4)来解决。

添加示例

解决方案也应该能够做到这一点- 文件1-

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

文件2-

BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB

输出-

ABBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
ABBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
ABBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

【问题讨论】:

  • 你能烘焙一个不应该被替换的行或列的例子吗?我无法通过范围获得您所指的内容(哪个值应该在范围内?)
  • 重要的是edit 反映您对 file1 中的范围 2-4 的意思
  • @VarunM 您的第一个示例在某处为假(结果与第 2 到第 5 列的 f2 具有相同的输出)。现在更不清楚您要实现的目标......
  • 我们在这里 :) 尝试使这两个文件足够不同以避免下次出现歧义(即:CA 一个数字列在两个文件中都相同,因此更难猜测):)
  • 当你说列时,大多数人会认为你的意思是字段,但是你谈论的字节让我认为你的意思是字符(并且你展示了一个似乎使用字符的例子)但是如果你的意思是characters 为什么你会说“bytes”而不是“characters”?请清理您的问题以选择 1 个术语(列、字节、字段或字符),如果是“字节”,请解释为什么“字符”不起作用。

标签: bash awk sed


【解决方案1】:

如果要替换列,只需将file1中的数据存储并替换到file2中即可:

$ awk 'FNR==NR {col1[FNR]=$1; col2[FNR]=$2; next} {$1=col1[FNR]; $2=col2[FNR]}1' f1 f2
1SER CA 1 17.704 15.987 17.370
2GLN CA 2 17.811 16.145 17.712
3ALA CA 3 17.634 16.267 18.034
4GLU CA 4 17.465 16.615 18.002

您还可以存储前两列的值,然后“手动”替换它们,如 delete a column with awk or sed 所示:

$ awk 'FNR==NR {data[FNR]=$1 OFS $2; next} {$0=gensub(/(\s*\S+){2}/,data[FNR],1)}1' f1 f2
1SER CA    1  17.704  15.987  17.370
2GLN CA    2  17.811  16.145  17.712
3ALA CA    3  17.634  16.267  18.034
4GLU CA    4  17.465  16.615  18.002

如果您只想替换某些字符,请使用substr() 提取这些字符:

$ awk -v start=2 -v len=3 'FNR==NR{data[FNR]=substr($0, start, len); next} {$0=substr($0, 1, 2) data[FNR] substr($0, start+len+1)}1' f2 f1
AABBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AABBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AABBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

即:

awk -v start=2 -v len=3 \
   'FNR==NR{data[FNR]=substr($0, start, len); next}             # store from the start-th to the (start+len)-th chars
    {$0=substr($0, 1, start) data[FNR] substr($0, start+len+1)} # replace those
    1' f2 f1                                                    # print what was created

【讨论】:

  • 谢谢,这解决了当前的问题,但不是我想要的。我不想将自己限制在列号上。我需要一个适用于某个范围的解决方案,而与列无关。我正在编辑我的示例以使其更难。
【解决方案2】:

pastecut 的解决方案

$ paste -d '' <(cut -c1 file1) <(cut -c2-4 file2) <(cut -c5- file1)
1MET     CA    1   1.401   0.040   0.887
2ARG     CA    2   1.708  -0.155   1.002
3ARG     CA    3   1.870  -0.103   0.662
4TYR     CA    4   1.829   0.274   0.695
ABBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
ABBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
ABBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

带变量:

$ s=10
$ e=25
$ paste -d '' <(cut -c1-$((s-1)) file1) <(cut -c"$s"-"$e" file2) <(cut -c$((e+1))- file1)
1SER     CA    1  17.704   0.040   0.887
2GLN     CA    2  17.811  -0.155   1.002
3ALA     CA    3  17.634  -0.103   0.662
4GLU     CA    4  17.465   0.274   0.695
AAAAAAAAABBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAA
AAAAAAAAABBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAA
AAAAAAAAABBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAA

【讨论】:

  • 比公认的答案更聪明:在恒定空间中运行。
【解决方案3】:

如果“列”和“字节”实际上是指“字符”,那么:

$ cat tst.awk
BEGIN {
    split(range,r,/-/)
    repS = r[1]
    repL = r[2] - r[1] + 1
    befL = repS - 1
    aftS = repS + repL
}
NR==FNR { rep[NR] = substr($0,repS,repL); next }
{ print substr($0,1,befL) rep[FNR] substr($0,aftS) }

$ awk -v range='2-4' -f tst.awk file2 file1
1MET     CA    1   1.401   0.040   0.887
2ARG     CA    2   1.708  -0.155   1.002
3ARG     CA    3   1.870  -0.103   0.662
4TYR     CA    4   1.829   0.274   0.695
ABBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
ABBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
ABBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

$ awk -v range='10-25' -f tst.awk file2 file1
1SER     CA    1  17.704   0.040   0.887
2GLN     CA    2  17.811  -0.155   1.002
3ALA     CA    3  17.634  -0.103   0.662
4GLU     CA    4  17.465   0.274   0.695
AAAAAAAAABBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAA
AAAAAAAAABBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAA
AAAAAAAAABBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAA

以上使用您的示例串联作为输入文件。

【讨论】:

    【解决方案4】:

    TXR Lisp 中的这个小超长单行从两个文件中获取两个惰性字符串列表,然后以所需的方式将它们组合成一个。 -t 选项将结果值(字符串列表)打印为行。

    $ txr -t '[apply mapcar* (ret `@{1 [0]}@{2 [1..4]}@{1 [4..:]}`)
                     (mapcar [chain open-file get-lines] *args*)]' file1 file2
    1MET     CA    1   1.401   0.040   0.887
    2ARG     CA    2   1.708  -0.155   1.002
    3ARG     CA    3   1.870  -0.103   0.662
    4TYR     CA    4   1.829   0.274   0.695
    

    原则上,这是一种非常类似于使用paste 将多个cut 流组合在一起的解决方案的方法(感谢 Bash 命令替换),除了所有流在一个进程中完成,使用数据结构和file1 不会被扫描两次。

    变量*args* 引用剩余的命令行参数,作为字符串列表。内部mapcar 通过open-fileget-lines 的链接映射这些字符串,以将它们作为文件打开,并从其每一行作为惰性字符串列表获取。然后将这两个惰性行列表应用于mapcar* 函数,通过(ret ...) 生成的匿名函数并行映射它们的元素。 ret 运算符构造一个匿名函数,其参数隐式派生自主体中出现的@1@2 编号参数。一个插值准字符串用于从左右字符串中选择字符的操作。 @1 会选择整个左边的字符串。大括号语法@{1 [0]} 表示它的第一个字符,@{2 [1..4]} 是切片提取。

    mapcar* 函数是 mapcar 的惰性版本,这很重要:这可以防止我们在打印输出之前在内存中构建整个输出。代码对惰性列表的输入进行操作,因此输入发生在映射通过这些列表行进时,并且行进是由-t 选项对输出列表的消耗驱动的。也就是说,表达式 instantlymapcar* 返回一个惰性列表,然后在打印该列表时(感谢-t 选项),该惰性列表的强制驱动生成它的映射,驱动惰性输入列表的消耗,驱动源文件的读取。

    我们可以看到ret表达式的扩展是什么样子的:

    $ txr -p '(macroexpand (quote (ret `@{1 [0]}@{2 [1..4]}@{1 [4..:]}`)))'
    (lambda (#:arg-01-0003
             #:arg-02-0004 . #:rest-0002)
      [identity (progn #:rest-0002
                  `@{#:arg-01-0003 [0]}@{#:arg-02-0004 [1..4]}@{#:arg-01-0003 [4..:]}`)])
    

    ret 已检查内容,钻入内插准字符串以找出“元数”参数。它注意到最高的是@2,因此生成了一个有两个参数的函数(带有一个用于尾随参数的rest参数)。参数作为临时符号生成,这些符号替换所有出现的编号变量。

    我们可以证明mapcar* 是惰性的,例如,将两个递增整数的无限列表相乘,然后只从结果中取前十个平方:

    $ txr -p '(take 10 [mapcar* * (range 0) (range 0)])'
    (0 1 4 9 16 25 36 49 64 81)
    

    【讨论】:

      【解决方案5】:

      你可以给

      join
      

      命令尝试:将行连接在一起,然后删除错误的列。

      join 需要对文件进行排序。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-08-10
        • 2012-08-25
        • 1970-01-01
        • 2021-12-20
        • 2015-03-02
        • 2016-07-12
        • 1970-01-01
        相关资源
        最近更新 更多