У меня есть файл с 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 пытается сопоставить gene
, 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
— вы с меньшей вероятностью сделаете ошибку, если вам нужно будет изменить его или добавить дополнительный код, если вам нужно сделать это только один раз, а не трижды. «Не повторяйтесь» не так уж важно в такой тривиальной маленькой программе, как эта, но может быть очень важно в более крупных программах. В любом случае, избегать/минимизировать повторения — это хорошая привычка программирования.
Если порядок сопоставления не имеет значения (т.е. если вам все равно, какой из них будет найден, главное, чтобы он был найден), то вы можете упростить скрипт:
#!/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
.
Теперь, если вы хотите иметь product
или sprot
в качестве альтернативных замен, вы можете использовать альтернативные строки расширенных регулярных выражений:
sed -E 's/gene_id "[^"]*"(.*(gene|product|sprot) ("[^"]*"))/gene_id \3\1/'
но это не будет предпочитать gene
over product
over 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
, product
и sprot` фиксирован, вы можете сначала извлечь предпочтительный идентификатор, одновременно помещая фактическую линию в пространство ожидания:
sed -E 'h;s/(sprot|product|gene) ("[^"]*").*/#\2/;s/.*#//;G;s/(.*)\n(.*gene_id )"[^"]*"/\2\1/'
Маркером #
может быть любая строка, которая, как известно, не является частью идентификатора; для GNU sed
вы можете использовать , \n
чтобы быть уверенным. Поэтому вы заменяете первую из указанных строк маркером и удаляете остальную часть строки, затем удаляете все до маркера, так что теперь в пространстве шаблона остается только идентификатор. Затем, с G
исходной строкой (которую мы сохранили в буфере удержания с помощью h
) будет добавлена, а затем идентификатор (часть до новой строки) заменяет "string"
после gene_id
. Как-то проще написать, чем объяснить.