sed: encontre o padrão e substitua por outro padrão na mesma linha

sed: encontre o padrão e substitua por outro padrão na mesma linha

Eu tenho um arquivo com gene_id e nomes de genes em uma linha. Quero substituir a palavra depoisgene_idcom a palavra depoisgeneou depoisprodutosou depoisbroto(se alguma coisa faltou).

Aqui está um exemplo de linha:

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

Eu tentei fazer isso com sed:

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

Mas os resultados estavam incorretos:

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

Mas eu quero salvar toda a linha, mas com outra palavra depoisgene_id, assim:

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

Ou assim (se outro faltou):

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

Qualquer ajuda será muito apreciada.

Responder1

O script perl a seguir tenta corresponder gene, product, e sprotem cada linha de entrada, nessa ordem (ou seja, ele prioriza gene em vez de produto e produto em vez de sprot). Se um deles for correspondido, ele extrai a palavra após a correspondência. Presume-se que a palavra esteja entre aspas duplas.

Se uma correspondência for encontrada, a palavra seguinte será substituída gene_idpela palavra extraída.

A linha é impressa independentemente de ter sido modificada ou não.

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

Alternativamente, isso poderia ser escrito para usar um loop para iterar sobre as palavras-chave correspondentes:

#!/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, esta versão é melhor porque é mais fácil de ler e entender (em particular, o foreachloop enfatiza que se trata de iterar uma lista de palavras). Mais importante ainda, evita a repetição da $word = $1instrução - é menos provável que você cometa um erro se precisar alterá-la ou adicionar código extra, se precisar fazer isso apenas uma vez, em vez de três vezes. "Don't Repeat Yourself" não é tão importante em um pequeno programa trivial como este, mas pode ser muito importante em programas maiores. De qualquer forma, evitar/minimizar a repetição é um bom hábito de programação.

Se a ordem da correspondência não fosse significativa (ou seja, se você não se importasse com qual foi encontrado, contanto que fosse), você poderia simplificar o script:

#!/usr/bin/perl

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

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

  print;
} 

Independentemente da versão do script que você usa, salve-o como, por exemplo replace.pl, e torne-o executável com chmod +x replace.pl. Ou experimente todos eles como replace1.pl, replace2.pl, replace3.pl. Então execute assim:

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

Responder2

Exploramos a propriedade de um hash de que, se vários valores forem aplicados a uma determinada chave, o último se tornará o valor final.

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

Como os comandos são todos iguais e apenas os nomes mudam, podemos facilmente fazer com que toda a tabela de operações seja controlada, onde apenas fornecemos os nomes dos campos enquanto o loop for cuidará do resto.

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

Responder3

Para completar a perlsolução, veja como você faria isso com sed. Não tenho certeza de como você espera que sua sintaxe funcione, mas na verdade você precisa de uma expressão regular para corresponder à string

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

gene_ide genesão combinados por si mesmos. Uma string entre aspas duplas é uma concatenação de aspas duplas, qualquer número de caracteres que não sejam aspas duplas ( [^"]*) e outras aspas duplas. Finalmente você tem o que está no meio.*

Agora você precisa colocar \(\)as peças que precisa reciclar na reposição:

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

O par externo cobre tudo o que deve ser deixado intacto. Isso é reutilizado como \1na substituição. O par interno é a string que você deseja reutilizar como gene_id.

Agora, se quiser ter productou sprotcomo substituições alternativas, você pode usar strings alternativas de expressões regulares estendidas:

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

mas isso não irá preferir , genemas preferirá o último deles que estiver presente. Se quiser ter essa ordem de preferência, você precisa de etapas separadas e começar pela última, para que possa ser substituída por uma melhor: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/'

Ou, se a ordem de gene, producte sprot` for fixa, você pode primeiro extrair o ID preferido enquanto estaciona a linha real no espaço de espera:

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

O marcador #pode ser qualquer sequência que não faça parte do ID; para GNU sedvocê pode usar \ncom certeza. Então você substitui a primeira das referidas strings pelo marcador e exclui o resto da linha, depois exclui tudo até o marcador, então agora apenas o ID permanece no espaço do padrão. Então, Ga linha original (que preservamos no buffer de retenção com h) será anexada e o ID (a parte antes da nova linha) substituirá o "string"depois gene_id. De alguma forma, é mais fácil escrever do que explicar.

informação relacionada