sed:尋找模式並取代同一行中的另一個模式

sed:尋找模式並取代同一行中的另一個模式

我有一個文件,其中gene_id 和基因名稱在一行中。我想替換後面的單字基因ID後面這個詞基因或之後產品或之後史普羅特(如果其中一些遺漏了)。

這是一行的範例:

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

以下 perl 腳本嘗試按順序匹配每個輸入行中的geneproduct、 和(即,它優先考慮基因優先於產品,優先考慮產品優先於 sprot)。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;
}

IMO,這個版本更好,因為它更容易閱讀和理解(特別是,循環foreach強調它是關於迭代單字清單)。更重要的是,它避免了重複該$word = $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.pl, replace2.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

現在,如果您想要使用productorsprot作為替代替換,您可以使用擴展正規表示式的替代字串:

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

但這不會優先選擇over gene,而是優先選擇最後一個存在的。如果您想獲得該優先順序,則需要單獨的步驟並從最後一個開始,以便可以將其替換為更好的步驟:productsprot

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),然後用 ID(換行符之前的部分)替換"string"after gene_id。不知怎的,寫起來比解釋容易。

相關內容