sed: パターンを検索し、同じ行内の別のパターンに置き換えます

sed: パターンを検索し、同じ行内の別のパターンに置き換えます

gene_idと遺伝子名が1行に書かれたファイルがあります。遺伝子IDafterという言葉で遺伝子またはそれ以降製品またはそれ以降スプロット(一部が欠落していた場合)。

以下に行の例を示します。

chrM    Gnomon  CDS 8345    8513    .   +   1   gene_id "cds-XP_008824843.3"; transcript_id "cds-XP_008824843.3"; Parent "rna-XM_008826621.3"; Dbxref "GeneID:103728653_Genbank:XP_008824843.3"; Name "XP_008824843.3"; end_range "8513,."; gbkey "CDS"; gene "semaphorin-3F"; partial "true"; product "semaphorin-3F"; protein_id "XP_008824843.3"; sprot "sp|Q13275|SEM3F_HUMAN";
chrM    StringTie   exon    2754    3700    .   +   .   gene_id "cds-YP_007626758.1"; transcript_id "cds-YP_007626758.1"; Parent "gene-ND1"; Dbxref "Genbank:YP_007626758.1,Gene "ID:15088436"; Name "YP_007626758.1"; Note "TAAstopcodoniscompletedbytheadditionof3'AresiduestothemRNA"; gbkey "CDS"; gene "ND1"; product "NADHdehydrogenasesubunit1"; protein_id "YP_007626758.1"; transl_except "(pos:3700..3700%2Caa:TERM)"; transl_table "2";

sedで作ってみました:

sed -E 's/[^gene_id] .*?;/[^gene] .*?;|[^sprot] .*?;|[^product] .*?;/g'

しかし、結果は間違っていました。

chrM    Gnomon  CDS 8345    8513    .   +   1   gene_id "cds-XP_008824843.3"[^gene] .*?;|[^sprot] .*?;|[^product] .*?;
chrM     StringTie       exon    2754    3700    .       +       .       gene_id "cds-YP_007626758.1"[^gene] .*?;|[^sprot] .*?;|[^product] .*?;

しかし、私はすべての行を保存したいのですが、その後に別の単語を追加します遺伝子ID、 このような:

chrM    Gnomon  CDS 8345    8513    .   +   1   gene_id "semaphorin-3F"; transcript_id "cds-XP_008824843.3"; Parent "rna-XM_008826621.3"; Dbxref "GeneID:103728653_Genbank:XP_008824843.3"; Name "XP_008824843.3"; end_range "8513,."; gbkey "CDS"; gene "semaphorin-3F"; partial "true"; product "semaphorin-3F"; protein_id "XP_008824843.3"; sprot "sp|Q13275|SEM3F_HUMAN";
chrM     StringTie       exon    2754    3700    .       +       .       gene_id "ND1"; transcript_id "cds-YP_007626758.1"; Parent "gene-ND1"; Dbxref "Genbank:YP_007626758.1,Gene "ID:15088436"; Name "YP_007626758.1"; Note "TAAstopcodoniscompletedbytheadditionof3'AresiduestothemRNA"; gbkey "CDS"; gene "ND1"; product "NADHdehydrogenasesubunit1"; protein_id "YP_007626758.1"; transl_except "(pos:3700..3700%2Caa:TERM)"; transl_table "2";

または、次のようになります (別の人が見逃した場合):

chrM    Gnomon  CDS 8345    8513    .   +   1   gene_id "sp|Q13275|SEM3F_HUMAN"; transcript_id "cds-XP_008824843.3"; Parent "rna-XM_008826621.3"; Dbxref "GeneID:103728653_Genbank:XP_008824843.3"; Name "XP_008824843.3"; end_range "8513,."; gbkey "CDS"; gene "semaphorin-3F"; partial "true"; product "semaphorin-3F"; protein_id "XP_008824843.3"; sprot "sp|Q13275|SEM3F_HUMAN";
chrM     StringTie       exon    2754    3700    .       +       .       gene_id "ND1"; transcript_id "cds-YP_007626758.1"; Parent "gene-ND1"; Dbxref "Genbank:YP_007626758.1,Gene "ID:15088436"; Name "YP_007626758.1"; Note "TAAstopcodoniscompletedbytheadditionof3'AresiduestothemRNA"; gbkey "CDS"; gene "ND1"; product "NADHdehydrogenasesubunit1"; protein_id "YP_007626758.1"; transl_except "(pos:3700..3700%2Caa:TERM)"; transl_table "2";

どのような助けでも大歓迎です。

答え1

gene次の Perl スクリプトは、各入力行でproduct、、をこの順序で一致させようとしますsprot(つまり、gene を product より優先し、product を sprot より優先します)。 いずれかが一致すると、一致後の単語を抽出します。単語は二重引用符で囲まれていると想定されます。

一致が見つかった場合は、その後の単語をgene_id抽出した単語に置き換えます。

変更されたかどうかに関係なく、行は印刷されます。

#!/usr/bin/perl

while (<>) {
  my $word = '';

  if (m/\b(?:gene)\s+("[^"]*")/) {
    $word = $1;
  } elsif (m/\b(?:product)\s+("[^"]*")/) {
    $word = $1;
  } elsif (m/\b(?:sprot)\s+("[^"]*")/) {
    $word = $1;
  };

  if ($word) {
    s/\bgene_id\s+(?:"[^"]*")/gene_id $word/
  };

  print;
} 

あるいは、ループを使用して一致キーワードを反復処理するように記述することもできます。

#!/usr/bin/perl

while (<>) {
  my $word = '';

  foreach my $match (qw(gene product sprot)) {
    if (m/\b(?:$match)\s+("[^"]*")/) {
      $word = $1;
      last; # first match wins, exit this loop
    }
  };

  if ($word) {
    s/\bgene_id\s+(?:"[^"]*")/gene_id $word/
  };

  print;
}

私の意見では、このバージョンの方が読みやすく理解しやすいので優れています (特に、ループforeachによって単語のリストを反復処理することが強調されます)。さらに重要なのは、ステートメントの繰り返しを避けられることです$word = $1。ステートメントを変更したり、コードを追加したりする必要がある場合、3 回ではなく 1 回だけ実行すれば済むため、エラーが発生する可能性が低くなります。「繰り返しをしない」ことは、このような小さなプログラムではそれほど重要ではありませんが、大規模なプログラムでは非常に重要になることがあります。繰り返しを回避/最小限に抑えることは、いずれにしても良いプログラミング習慣です。

一致の順序が重要でない場合 (つまり、どれが見つかったかは気にせず、1 つだけ見つかればよい場合)、スクリプトを簡略化できます。

#!/usr/bin/perl

while (<>) {
  my ($word) = m/\b(?:gene|product|sprot)\s+("[^"]*")/;

  if ($word) {
    s/\bgene_id\s+(?:"[^"]*")/gene_id $word/
  };

  print;
} 

使用するスクリプトのバージョンに関係なく、たとえば として保存しreplace.pl、 で実行可能にしますchmod +x replace.pl。または、すべてを 、 、 として試してくださいreplace1.plreplace2.pl次にreplace3.pl、次のように実行します。

$ ./replace.pl input.txt 
chrM    Gnomon  CDS 8345    8513    .   +   1   gene_id "semaphorin-3F"; transcript_id "cds-XP_008824843.3"; Parent "rna-XM_008826621.3"; Dbxref "GeneID:103728653_Genbank:XP_008824843.3"; Name "XP_008824843.3"; end_range "8513,."; gbkey "CDS"; gene "semaphorin-3F"; partial "true"; product "semaphorin-3F"; protein_id "XP_008824843.3"; sprot "sp|Q13275|SEM3F_HUMAN";
chrM    StringTie   exon    2754    3700    .   +   .   gene_id "ND1"; transcript_id "cds-YP_007626758.1"; Parent "gene-ND1"; Dbxref "Genbank:YP_007626758.1,Gene "ID:15088436"; Name "YP_007626758.1"; Note "TAAstopcodoniscompletedbytheadditionof3'AresiduestothemRNA"; gbkey "CDS"; gene "ND1"; product "NADHdehydrogenasesubunit1"; protein_id "YP_007626758.1"; transl_except "(pos:3700..3700%2Caa:TERM)"; transl_table "2";

答え2

特定のキーに複数の値が適用されると、最後の値が最終値になるというハッシュの特性を利用します。

perl -lpe 'my($l,%h)=($_);
  $h{gene_id}=$_ for map {
     $l =~ /\b$_\s+(".*?");/
  } reverse qw(gene product sprot);
  s/\bgene_id\s+\K".*?";/$h{gene_id};/;
' your_file_genes

コマンドはすべて同じで、名前だけが変わるので、操作全体をテーブル駆動にするのが簡単にできます。フィールド名を指定するだけで、残りの処理は for ループが処理します。

for i in gene product sprot;do
  cat - <<\_FMT_ |\
  sed -e "s/%s/$i/"
s/(\<gene_id\s+)"[^"]*"(.*\s%s\s+("[^"]*"))/\1\3\2/;t
_FMT_
done | sed -Ef - your_file_genes

答え3

解決策を完成させるためにperl、次のようにしますsed。与えられた構文がどのように機能すると期待されているかわかりませんが、実際には文字列に一致する正規表現が必要です。

... gene_id "remove me" ... some other stuff gene "replacement" ... more stuff
    =======                                  ====
    gene_id   "[^"]*"        .*              gene    "[^"]*"

gene_idおよびはgeneそれ自体で一致します。二重引用符で囲まれた文字列は、二重引用符、二重引用符以外の任意の数の文字([^"]*)、および別の二重引用符の連結です。最後に、その間にあるもの.*

\(\)次に、交換品にリサイクルする必要がある部品を配置する必要があります。

sed 's/gene_id "[^"]*"\(.* gene \("[^"]*"\)\)/gene_id \2\1/'

外側のペアは、変更しないでおくべきすべての部分をカバーします。これは、\1置換で再利用されます。内側のペアは、 として再利用する文字列ですgene_id

productここで、または を代替の置換として使用したい場合はsprot、拡張正規表現の代替文字列を使用できます。

sed -E 's/gene_id "[^"]*"(.*(gene|product|sprot) ("[^"]*"))/gene_id \3\1/'

しかし、これは をgeneより優先するproductのでsprotはなく、存在する最後のものを優先します。その優先順位にしたい場合は、別の手順が必要であり、最後のものから始めて、より良いものに置き換えることができます。

sed 's/gene_id "[^"]*"\(.* sprot \("[^"]*"\)\)/gene_id \2\1/
     s/gene_id "[^"]*"\(.* product \("[^"]*"\)\)/gene_id \2\1/
     s/gene_id "[^"]*"\(.* gene \("[^"]*"\)\)/gene_id \2\1/'

geneまたは、、およびsprot`の順序がproduct固定されていることがわかっている場合は、実際の行をホールド スペースにパーキングしながら、最初に優先 ID を抽出できます。

sed -E 'h;s/(sprot|product|gene) ("[^"]*").*/#\2/;s/.*#//;G;s/(.*)\n(.*gene_id )"[^"]*"/\2\1/' 

マーカーは、#ID の一部ではないことがわかっている任意の文字列にすることができます。GNU では、確実にするためにsedを使用できます\n。したがって、前述の文字列の最初の部分を マーカーで置き換え、行の残りの部分を削除してから、マーカーまでのすべてを削除します。これで、パターン スペースには ID だけが残ります。次に、 でGホールド バッファーに保持した元の行hが追加され、その後、"string"の後の部分が ID (改行の前の部分) に置き換えられますgene_id。説明するよりも、書く方が簡単です。

関連情報