【问题标题】:Replace a string only if the next line has another specific string仅当下一行有另一个特定字符串时才替换字符串
【发布时间】:2020-05-04 18:55:11
【问题描述】:

我正在尝试替换一行上的字符串,前提是下一行具有特定字符串。

我有:

13963   12602   gene
13963   12602   rRNA

结果需要是

13963   12602   rRNA
13963   12602   rRNA

数字不同的地方... 所以我尝试了:

VAR_1=$1

if [grep rRNA -B1  $1 |awk '{print $3}' |head -n1 ='gene']
 then
   sed 's/gene/rRNA'
fi

我得到了

line 5: [grep: command not found
head: =gene]: No such file or directory

而且我不知道从这里去哪里......

【问题讨论】:

  • 所以一切都是硬编码的?如果下一行是 rRNA 并且这一行有 gene 则将 gene 更改为 rRNA?
  • 是的。就是这样。
  • 如果只是总是打印rRNA作为第三个字段不是你所需要的,那么edit你的问题是提供更具代表性的样本输入和预期输出,包括第三个输出字段不能为rRNA 的情况。现在awk '{$3="rRNA"}1' file 会从您的样本输入中产生您预期的输出,所以如果这还不是全部,那么这不是一个很好的例子。
  • 必须gene,或rRNA,或两者,必须是完整字,或者它们可以是较长字的子串?例如eugenegeneric - 这算作gene 吗?另外-假设您真的是指“完整的单词”-它们总是在行尾,还是可以出现在任何地方? gene 是否可以多次出现,如果是,是否需要替换该行上的所有出现? generRNA 是否可以出现在同一行上,如果可以,所需的处理方式是什么?
  • ... 顺便说一句 - 这两行的数字是否需要匹配?还是您的样本中纯属巧合?

标签: linux bash awk sed grep


【解决方案1】:

您能否尝试仅使用所示示例进行跟踪、编写和测试。

awk '
FNR==NR{
  if($NF=="rRNA"){
    a[FNR]=$NF
  }
  next
}
((FNR+1) in a){
  $NF=a[FNR+1]
}
1
'  Input_file  Input_file | column -t

或者,如果您想在 awk 中有一个变量,您可以在其中定义要在 Input_file 中查看的字符串,然后尝试以下操作。

awk -v str="rRNA" '
FNR==NR{
  if($NF==str){
    a[FNR]=$NF
  }
  next
}
((FNR+1) in a){
  $NF=a[FNR+1]
}
1
'  Input_file  Input_file | column -t

逻辑解释:

  • 在此处读取 Input_file 2 次。
  • 当第一次读取 Input_file 时,它​​会查找最后一个字段中包含字符串 rRNA 的行。
  • 然后它创建一个数组,其索引是该行号,值是该行的最后一个字段。
  • next 将跳过所有进一步的语句
  • 现在检查条件(FNR+1) in a,将在第二次读取 Input_file 时检查该条件。它正在检查当前行的行号 + 1 是否存在于数组中。
  • 如果它进入数组,则将数组值设置为当前行的最后一个字段
  • 1 将打印行。

【讨论】:

  • @SilviaJusti,现在也添加了详细的级别解释,如果有任何疑问,请告诉我。
  • 读取输入文件两次?效率不是很高,是吗?
  • @mathguy,嗨,是的,我可以创建一个数组并且也会这样做,但为了简单起见,我这样做了。但是将整个文件存储到一个数组中,最后在冷端块中使用它,恕我直言,内存占用太高了。由于 OP 没有提到大小,所以我选择简单的逻辑之一 :)
  • 这个问题承认至少有一种解决方案(我相信还有更多,包括使用awk),其中编辑是在从文件中流式传输数据时完成的。我在我的解决方案中展示了一种这样的方法,使用sed。在这个问题中,文件完全有可能很小,所以没关系 - 但它也可能非常大。
【解决方案2】:

sed 将换行符视为纯字符,这是选项-z
gene 后跟换行符时,在下一行(直到下一个换行符的子字符串)字符串rRNA 位于行尾时,替换gene

sed -rz 's/gene\n([^\n]*)(rRNA\n)/\2\1\2/g' "$1"

【讨论】:

  • 这几乎否定了流处理,不是吗?整个输入文件需要作为单个“行”来读取和处理。
  • @mathguy OP 在他的代码尝试中使用$1 作为输入文件,所以我忽略了流式传输问题并希望文件大小有限。
  • 这与流媒体有什么关系?使用 $1 仅仅意味着 OP 想要将文件名作为输入传递,而不是像我一样对其进行硬编码;为什么它本身会否定流处理?
  • 对于流式传输,我想通过管道传输其他进程的输出,longlastingprocess | showresultsastheycome。第一个过程可以在几个小时后完成。对于awk ... Input_file Input_file 解决方案,这种流式传输情况将更加困难。你说得对,流式传输也适用于文件。
【解决方案3】:

这是一个sed 解决方案,具有以下假设:您正在寻找固定单词generRNA(作为完整单词,而不是单词片段)出现在第一个末尾的连续行,分别第二行,在这些情况下,将gene 替换为rRNA。这些词前面的数字不起作用。 (如果必须匹配,也可以安排。)

首先看一下我为测试特殊情况而创建的输入文件:

$cat ff

13960   12602   gene
13963   12602   rRNA
10030   24022   general
10040   32002   rRNA
30902   32098   gene
34003   30934   gene
40902   30921   rRNA
42093   40023   zymogene
34020           rRNA
30202   10030   para
40302   20030   gene
               crRNA

这里的gene 将在两行被rRNA 替换:以数字 13960 和 34003 开头的行。注意以 42093 开头的行 - gene 出现在末尾,但不是完整的词,不应被替换。最后一行也以字符串rRNA 结尾,但由于这是一个单词片段,而不是一个完整的单词,所以上面的gene 不应被替换。

然后:

$ sed '{N;s/\bgene\(\n.*\brRNA\)$/rRNA\1/;P;D}' ff

13963   12602   rRNA
13963   12602   rRNA
10030   24022   general
10040   32002   rRNA
30902   32098   gene
34003   30934   rRNA
40902   30921   rRNA
42093   40023   zymogene
34020           rRNA
30202   10030   para
40302   20030   gene
               crRNA

处理从第一行开始。在每一步:N 向模式空间添加一个换行符和下一行输入; sgene 替换为 rRNA 如果第一行以完整单词 gene 结尾,第二行以完整单词 rRNA 结尾(注意使用 \b 断言); P 打印模式空间的第一行(无论是保持不变还是经历了gene rRNA 替换); D 然后从模式空间中删除第一行(包括换行符)。然后循环再次开始:N 在模式空间中添加一个换行符和下一个输入行,等等。当我们到达最后一行时,N 什么都不做(因为文件中没有更多的行); s 命令将自动查找不匹配,P 将打印最后一行,D 将删除它,我们就完成了。请注意,在 POSIX 标准中,在最后一行,如果 N 没有找到“下一个”行,则 sed 终止(因此最后一行不是 Printed);所以这个解决方案充分利用了N 命令的这个GNU 扩展。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-19
    • 2021-01-25
    • 2017-08-09
    • 2013-12-03
    • 2017-04-18
    • 1970-01-01
    • 2019-05-14
    相关资源
    最近更新 更多