Extrai várias instâncias do texto entre duas palavras, inclusive da palavra inicial, mas exclusivas da palavra final

2

Um arquivo PDB contém vários parágrafos de conformações de uma proteína.

Cada conformação começa com a palavra-chave ATOM e termina com a palavra-chave END .

Estou tentando ler o arquivo no bash, de modo que eu leio todas as linhas do ATOM até o END, mas não quero ler a palavra END.

Eu quero fazer isso para cada conformação (parágrafo) e armazenar cada parágrafo em uma matriz.

O arquivo tem a seguinte aparência:

ATOM line 1...

ATOM line 2...

ATOM line 3...

# More lines....

END

ATOM line 1...

ATOM line 2...

ATOM line 3...

# more lines...

END

Um ATOM para END é uma conformação.

Eu quero ser capaz de ler cada conformação em uma matriz, incluindo o ATOM, mas excluindo o END .

Eu posso ler texto entre duas palavras-chave exclusivas das duas palavras, mas não sei como incluir a palavra inicial, mas excluir a palavra final.

Também lendo cada conformação em uma matriz tal que conf[0] = primeira conformação, conf[1] = segunda conformação assim por diante e assim por diante não funcionem.

Código:

#!/bin/bash

filename='coor.pdb'
echo Start
i=0
while read line; do
    conf[$i]=$(sed -n '/ATOM/,/END/{//!p}') 
    i=i+1           
done < $filename
echo $conf[0] > first_frame.data
    
por shay 20.08.2015 / 17:11

2 respostas

2

#!/bin/bash

filename='coor.pdb'
echo Start
i=1
input=false
while read -r line
do
    if [ "${line%% *}" == "ATOM" ]
    then
        input=true
    elif [ "${line%% *}" == "END" ]
    then
        ((i++))                 # increase variable i by 1 == (i+1)
        rm -f "${i}_frame.data" # remove ${i}_frame.data" if exist
        input=false             # stop output lines until next ATOM
    fi
    if $input                   # if var INPUT is true add line to ${i}_frame.data file
    then
        echo "$line" >> "${i}_frame.data"
    fi
done < "$filename"

Para futuras sugestões do sed :

sed '/ATOM/,/END/!d;/END/d'
sed -n '/ATOM/{:;N;s/\nEND//;T;p}'

Então você pode fazer a tarefa:

nl -s'.frame.data' -b p"^END" coor.pdb | 
sed -n '/ATOM/{s/^/echo \"/;:;s/ \{6,\}//;N;s/END//;T;s/\n  */\">/p}' |
bash
    
por 20.08.2015 / 17:49
2

O processamento de texto no bash é lento. A manipulação de string pura é boa para o texto que você já possui em variáveis, ou para ler arquivos muito pequenos. Eu suspeito que os arquivos de biologia computacional normalmente não serão pequenos, então use uma ferramenta como awk , que tem um custo de inicialização pequeno, mas processa o texto muito mais rapidamente que o bash.

Supondo que você realmente queira apenas dividir seu arquivo pdb :

awk -v RS='\nEND\n' '{ fn="frame" NR ".pdb"; print > fn; close(fn) }' "$filename"

Obtenha o awk para usar \nEND\n como o separador de registro de entrada, em vez de nova linha, e você pode até mesmo usar seu contador de registros. O separador de registro de saída ainda é o padrão ORS="\n" . (sugestão muito legal de Costas. Eu ajustei-o assim END tem que estar no começo de uma linha, e adicionei close para ter certeza que não usa uma tonelada de descritores de arquivo em entradas com muitas conformações. )

Minha ideia original era:

awk 'BEGIN{i=0; fn="frame0.pdb"}
     !/^END/ { print > fn; }
     /^END/{ close(fn); fn="frame" ++i ".pdb"; }' \
     "$filename"

O awk armazena em cache as manipulações de arquivos, portanto, vários print > fn não resultam na reabertura do arquivo. ( close(fn) faz isso. Está lá apenas por eficiência, então o awk não acaba com arquivos de arquivos abertos).

A lógica é: imprimir toda a linha completa para o nome do arquivo atual. Quando você vir uma linha END , vá para o próximo nome de arquivo. Se não houver outra linha após o último END , o novo nome do arquivo nunca será gravado e nenhum último arquivo vestigial será criado.

OTOH, se você quiser fazer algo com uma matriz de blocos de linha na memória:

# add a '!/^END/' condition to the concat block if you want to avoid a stray newline after each END
awk 'BEGIN{i=0}  
     !/^END/ { arr[i] = arr[i] $0 "\n"; }  # concat onto this array element
     /^END/ { i++; }
     END{for (k in arr) { print arr[k]; > ("frame" k ".pdb") } }' \
"$filename"

Então você tem um array de linhas para fazer o que quiser no bloco END . Tem boas funções regex.

Falha na tentativa no bash de executar o sed (nvm, falha porque sed não lê um byte de cada vez da maneira como o shell read faz):

i=0
while true; do
    outf="frame${i}.data";
    ##### DON'T USE THIS, sed READS TOO MUCH #####
    strace -o sed.tr sed '/^END/q42' > "$outf";  # strace to see that the 2nd sed invocation finds the file empty
    ret=$?;
    ((i++));
    if [[ $ret == 0 ]];then  # sed didn't see END before EOF
        [[ -s $outf ]] || rm -f "$outf";  # clean up empty last file
        break;
    elif [[ $ret != 42 ]]; then
        echo some other sed error;
        break;
    fi;
done < "$filename"
    
por 20.08.2015 / 20:29