sed: Muster finden und durch ein anderes Muster in der gleichen Zeile ersetzen

sed: Muster finden und durch ein anderes Muster in der gleichen Zeile ersetzen

Ich habe eine Datei mit gene_id und Gennamen in einer Zeile. Ich möchte das Wort nach ersetzenGen-IDmit dem Wort nachGenoder danachProduktoder danachsprot(falls etwas davon fehlt).

Hier ist ein Beispiel für eine Zeile:

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";

Ich habe versucht, es mit sed zu machen:

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

Die Ergebnisse waren jedoch falsch:

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] .*?;

Aber ich möchte alle Zeilen speichern, aber mit einem anderen Wort danachGen-ID, so was:

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";

Oder so (falls noch jemand es verpasst hat):

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";

Jede Hilfe wird sehr geschätzt.

Antwort1

geneDas folgende Perl-Skript versucht, in jeder Eingabezeile , product, und in dieser Reihenfolge zuzuordnen sprot(d. h. es priorisiert Gene vor Product und Product vor Sprot). Wenn eines davon übereinstimmt, extrahiert es das Wort nach der Übereinstimmung. Es wird angenommen, dass das Wort in Anführungszeichen eingeschlossen ist.

Wenn eine Übereinstimmung gefunden wurde, wird das nachfolgende Wort gene_iddurch das extrahierte Wort ersetzt.

Die Zeile wird gedruckt, unabhängig davon, ob sie geändert wurde oder nicht.

#!/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;
} 

Alternativ könnte dies so geschrieben werden, dass eine Schleife zum Durchlaufen der Übereinstimmungsschlüsselwörter verwendet wird:

#!/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;
}

Meiner Meinung nach ist diese Version besser, weil sie leichter zu lesen und zu verstehen ist (insbesondere die foreachSchleife betont, dass es darum geht, über eine Liste von Wörtern zu iterieren). Noch wichtiger ist, dass die Wiederholung der $word = $1Anweisung vermieden wird – Sie machen weniger wahrscheinlich einen Fehler, wenn Sie sie ändern oder zusätzlichen Code hinzufügen müssen, wenn Sie es nur einmal statt dreimal tun müssen. „Wiederholen Sie sich nicht“ ist in einem trivialen kleinen Programm wie diesem nicht so wichtig, kann aber in größeren Programmen sehr wichtig sein. Wiederholungen zu vermeiden/minimieren ist ohnehin eine gute Programmiergewohnheit.

Wenn die Reihenfolge der Übereinstimmungen nicht von Bedeutung wäre (d. h. wenn es Ihnen egal wäre, welches Ergebnis gefunden wird, solange eines gefunden wird), könnten Sie das Skript vereinfachen:

#!/usr/bin/perl

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

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

  print;
} 

Unabhängig davon, welche Version des Skripts Sie verwenden, speichern Sie es beispielsweise als replace.plund machen Sie es mit ausführbar chmod +x replace.pl. Oder probieren Sie sie alle als replace1.pl, replace2.pl, replace3.plaus. Führen Sie es dann folgendermaßen aus:

$ ./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";

Antwort2

Wir nutzen die Eigenschaft eines Hashs, dass, wenn auf einen gegebenen Schlüssel mehrere Werte angewendet werden, der letzte der endgültige Wert wird.

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

Da die Befehle alle gleich sind und sich nur die Namen ändern, können wir die gesamte Operation problemlos tabellengesteuert gestalten, wobei wir nur die Feldnamen bereitstellen, während die For-Schleife sich um den Rest kümmert.

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

Antwort3

Um die perlLösung zu vervollständigen, hier ist, wie Sie es mit machen würden sed. Ich bin nicht sicher, wie Sie erwarten, dass Ihre angegebene Syntax funktioniert, aber eigentlich benötigen Sie einen regulären Ausdruck, der mit der Zeichenfolge übereinstimmt

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

gene_idund genewerden mit sich selbst abgeglichen. Ein String in Anführungszeichen ist eine Aneinanderreihung eines Anführungszeichens, einer beliebigen Anzahl von Zeichen, die keine Anführungszeichen sind ( [^"]*) und eines weiteren Anführungszeichens. Schließlich haben Sie das Zeug dazwischen.*

Nun müssen Sie \(\)im Ersatzteil die Teile zusammenstellen, die Sie recyceln möchten:

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

Das äußere Paar umfasst alles, was unberührt bleiben soll. Dies wird wie \1beim Ersetzen wiederverwendet. Das innere Paar ist die Zeichenfolge, die Sie als wiederverwenden möchten gene_id.

productWenn Sie nun alternative Ersetzungen wünschen sprot, können Sie alternative Zeichenfolgen erweiterter regulärer Ausdrücke verwenden:

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

aber dies wird nicht over bevorzugen gene, sondern das letzte der vorhandenen. Wenn Sie diese Bevorzugungsreihenfolge haben möchten, müssen Sie separate Schritte durchführen und mit dem letzten beginnen, damit es durch ein besseres ersetzt werden kann 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/'

Oder, wenn bekannt ist, dass die Reihenfolge von gene, productund sprot` fest ist, können Sie zuerst die bevorzugte ID extrahieren, während Sie die eigentliche Zeile im Haltebereich parken:

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

Der Marker #kann jede beliebige Zeichenfolge sein, von der bekannt ist, dass sie nicht Teil der ID ist; für GNU sedkönnen Sie \nzur Sicherheit verwenden. Sie ersetzen also die erste der besagten Zeichenfolgen durch den Marker und löschen den Rest der Zeile. Dann löschen Sie alles bis zum Marker, sodass jetzt nur noch die ID im Musterbereich übrig ist. Dann wird mit Gdie ursprüngliche Zeile (die wir mit im Hold-Puffer beibehalten haben h) angehängt und dann ersetzt die ID (der Teil vor der neuen Zeile) das "string"nach gene_id. Irgendwie leichter zu schreiben als zu erklären.

verwandte Informationen