使用 sed 替换不按预期工作

使用 sed 替换不按预期工作

我需要在大型 ascii 文本文件上运行搜索/替换功能。输入文件的简短摘录:

gene_id "MSTRG.1";
gene_id "MSTRG.1";
gene_id "MSTRG.2";
gene_id "MSTRG.3";

MSTRG字符串将替换为模板文件中存在的另一个 ID:

MSTRG.1 AT1G01030
MSTRG.2 AT1G01010
MSTRG.3 AT1G01035

一个简单的 while 循环迭代模板的每一行并进行替换:

while read bef aft
do
  echo "Searching for $bef"
  echo "Replacing with $aft"
  sed "s/$bef/$aft/g" input > output
done < template

发生的情况是,MSTRG.2后面的条目被正确替换,但MSTRG.1保持不变。输出如下所示:

gene_id "MSTRG.1";
gene_id "MSTRG.1";
gene_id "AT1G01010";
gene_id "AT1G01035"; 

更新

这就是我最终所做的。

while read bef aft
do
  sed -i "s/$bef/$aft/g" input
done < template

答案1

您的问题是,您在循环的每次迭代中都破坏了输出文件,只留下最近的更改,output而不保留早期的更改。

相反,您可以轻松地将template文件转换为一系列sed命令:

$ awk '{ printf("s/%s/%s/g\n", $1, $2) }' template
s/MSTRG.1/AT1G01030/g
s/MSTRG.2/AT1G01010/g
s/MSTRG.3/AT1G01035/g

...然后将它们应用到您的文件中:

$ awk '{ printf("s/%s/%s/g\n", $1, $2) }' template | sed -f - input
gene_id "AT1G01030";
gene_id "AT1G01030";
gene_id "AT1G01010";
gene_id "AT1G01035";

的某些实现sed不识别-为标准输入的含义。要将此方法与此类 a 一起使用sed,请替换-f --f /dev/stdin

或者,您可以在以下位置完成这一切awk

$ awk 'FNR == NR { pat[$1] = $2; next } { for (p in pat) gsub(p, pat[p]); print }' template input
gene_id "AT1G01030";
gene_id "AT1G01030";
gene_id "AT1G01010";
gene_id "AT1G01035";

请注意,上述所有变体都使用第一列中的内容template作为正则表达式,意味着.(点)将匹配任何特点。

答案2

您可以将输入文件复制为输出文件并对此输出文件进行操作,而不是在每次循环迭代中覆盖输出文件。

Withsed-i选项更改会就地写入同一文件,因此以前的替换不会丢失:

cp input output
while read bef aft
do
  echo "Searching for $bef"
  echo "Replacing with $aft"
  sed -i "s/$bef/$aft/g" output
done < template

答案3

#!/usr/bin/perl -i

use strict;

# The %re hash holds the regexp searches and replacement strings.
my %re = ();

my $tfile = shift;
open(TEMPLATE, "<", $tfile) || die "couldn't open $tfile for read: $!\n";
while(<TEMPLATE>) {
   chomp;
   my ($search,$replace) = split;
   $re{qr/$search/} = $replace;
};
close(TEMPLATE);

while (<>) {
  foreach my $s (keys %re) {
    s/$s/$re{$s}/g;
  };
  print;
}

这会读取template文件并构建一个称为%re正则表达式搜索和替换的关联数组(又名“哈希”)。

然后它循环遍历命令行上的每个剩余文件名(例如input),并在每一行输入上执行所有这些搜索和替换操作。它用于qr//预编译正则表达式 - 如果 中没有很多行,这只是一个微不足道的优化template,但如果有很多行,则可以导致非常显着的加速。

on -ithe #!/usr/bin/perl -iline 导致 perl 对输入文件进行就地编辑,而不是仅仅将更改打印到 stdout。例如,-i.bak如果您希望它在文件更改之前保留文件的备份副本,请将其更改为。

另存为,例如,cryptic0.pl使其可执行chmod +x cryptic0.pl并像这样运行它:

$ ./cryptic0.pl template input

该脚本不会在终端上产生任何输出。相反,它将编辑输入文件。

例如,您的input文件将更改为:

$ cat input
gene_id "AT1G01030";
gene_id "AT1G01030";
gene_id "AT1G01010";
gene_id "AT1G01035";

顺便说一句,此脚本会将所有行上的所有匹配项更改为其适当的替换字符串。如果你确定只能有匹配任何给定的行,您可以通过更改此行来加快速度:

s/$s/$re{$s}/g;

对此:

s/$s/$re{$s}/ && last;

这会导致脚本跳出 foreach 循环到该print语句,然后在成功搜索和替换后立即转到下一个输入行。


顺便说一句,请参阅为什么使用 shell 循环处理文本被认为是不好的做法?为什么用 sh 循环进行文本处理不是一个好主意。使用awkorperlsed或其他任何内容代替shor bash

相关内容