[Resolvido] Dividir um arquivo em linhas que contém um texto específico

Olá pessoal, boa noite!

Eu tenho um arquivo de texto (texto.out) que é composto por vários "blocos". Eu preciso dividir esse arquivo de forma que cada bloco vá para um novo arquivo menor. Porém, cada um desses blocos tem diferentes números de linhas (alguns tem 100, outro 300, não há uma regra quanto a isso). Porém, todos os blocos terminam com o mesmo texto que não se repete em outras partes do arquivo: "END OF GFF DUMP". Então, eu pensei em usar esse "sinal" para delimitar os blocos e divir o arquivo. Pensei em usar while com grep pra fazer isso. Consegui uma lista com o número da linha de todas as vezes que esse sinal aparece no arquivo, mas daí até usar essa informação para dividí-lo está bem difícil pra mim. Segue o código abaixo:

#variables
lines=$(grep -n "END OF GFF DUMP" texto.out | wc -l)

#generate the list with the number of the lines with "END OF GFF DUMP"
i=1
while [ $i -le $lines ];
do
declare -i | grep -n 'END OF GFF DUMP' texto.out | awk -F ':' '{print $1}' | head -n $i | tail -n 1
((i=$i+1))
done

Rodando esse script ele me retorna:

169
437
720
988
1261
1569
1862
2155
2408

...que são os números das linhas que eu tenho interesse no arquivo texto.out. Eu estava querendo fazer algo mais ou menos assim: "$ head -n x texto.out | tail -n 1 > texto$i.out" onde o x seria cada número dessa lista, se possível, colocar isso dentro do while, mas não está dando certo. Alguém tem alguma dica?

Muito obrigado!!

Comentários

  • pelo que esta pedindo muito provavelmente vc conseguiria fazê-lo com o sed, mas com a informação que vc passou eu nao tenho certeza, seria interessante vc postar algumas linhas do arquivo para vermos como filtrá-lo.

    vc quer tipo pegar as primeiras 100 linhas e jogar em outro arquivo ou pegar a primeira coluna das primeiras 100 linhas e jogar em outro arquivo?

    sempre quando vou tratar algum dado que não tem uma quantidade certa de linhas, arquivos etc eu dou um ls, cat etc para ler o conteudo e coloco o | wc -l para contar a quantidade de saidas, assim posso determinar quantos loops serão necessarios.

    LINHAS=$"0" # Determina o ponto de partida da contagem
    TOTAL_LINHAS=$(cat $ENDERECO_DO_ARQUIVO | wc -l) # identifica quantas linhas tem o arquivo, e consequentemente quantos loops terá o while
    while [ $LINHAS -ne $TOTAL_LINHAS ];do # a cada loop processa uma linha
    LINHAS=$[ $LINHAS + 1 ] # vai somando para não dar loop infinito

    # o codigo que faz a filtragem do texto

    done # fecha o while

    no codigo acima eu conseguiria ler apenas um arquivo, para ele ler todos de uma pasta vc deve fazer outro loop deixando este por dentro, e neste outro loop vc usaria a mesma logica para definir a quantidade de rodadas.

    se for o caso de ler linha por linha a cada loop deve se ler uma linha diferente então eu uso a variavel LINHAS que vai somando a cada loop, uso esta variavel no sed:
    LINHA_ ATUAL=$(cat $ENDERECO | sed -n ${LINHAS}p) # linha atual

    existe formas bem mais simples de se escrever estes loops, mas eu sei fazer assim.
  • editado maio 2019
    Olá luiz99, muito obrigado pela sua resposta e pelas dicas!! Vou estudá-las com cuidado pra ver onde consigo melhorar o meu código. Sou novo no bash e não entendo muito bem, mas, fuçando aqui eu consegui resolver, provavelmente, não dá melhor maneira possível, mas tá rodando (espero que sem bugs kkkk)

    O arquivo onde quero pesquisar é esse (ele é um output de outro programa, é um arquivo de texto bem complicadinho):

    C4 Alignment:
    ------------
    Query: xx-like-aa
    Target: scaffoldxx|size867476
    Model: protein2genome:bestfit
    Raw score: 166
    Query range: 0 -> 407
    Target range: 202895 -> 302143

    1 : GluLysMetProAsnSerAsnArgLeuTyrThrLeuAspGluPheLeuIleLysLeuG : 20
    ! ! |||:::! !..!|||!!!|||! !! !||| !||| !||| |||.
    LeuValAsnProSerPheArgArgPheTyrArgSerAspProPheGluIleCysLeuA
    202896 : TTAGTTAATCCGTCATTCCGTCGTTTTTATAGATCCGATCCATTTGAGATTTGTTTGA : 202953
    .
    .
    .
    scaffold210|size867476 exonerate:protein2genome:bestfit splice3 302097 302098 . + . intron_id 3 ; splice_site "AG"
    scaffold210|size867476 exonerate:protein2genome:bestfit cds 302099 302143 . + .
    scaffold210|size867476 exonerate:protein2genome:bestfit exon 302099 302143 . + . insertions 0 ; deletions 0
    scaffold210|size867476 exonerate:protein2genome:bestfit similarity 202896 302143 166 + . alignment_id 1 ; Query Cmeg-Or88a-like-aa ; Align 202896 1 78 ; Align 300821 28 153 ; $
    # --- END OF GFF DUMP ---
    #

    C4 Alignment:
    ------------
    Query: xx-like-aa
    Target: scaffoldxx|size867476
    Model: protein2genome:bestfit
    Raw score: 83
    Query range: 0 -> 407
    Target range: 72802 -> 822917
    .
    .
    .
    Ele é composto de vários blocos que terminam com essa frase: "END OF GFF DUMP". Eu preciso dividi-lo em n arquivos, cada um tendo um desses blocos que variam em número dentro do arquivo principal e em número de linhas. Eu fiz dessa maneira (me desculpe se tiver muito confuso):

    #!/bin/bash

    #Defino lines com o número total de blocos que há no arquivo
    lines=$(grep -n "END OF GFF DUMP" texto.out | wc -l)

    #Dividir o arquivo texto.out em seus diferentes blocos baseado na linha "END OF GFF DUMP"
    i=1
    while [ $i -le $lines ];
    do
    #Defini "a" que seria o número da linha do segundo "END OF GFF DUMP" encontrado e "b" que seria o primeiro.
    a=$(grep -n "END OF GFF DUMP" texto.out | cut -d: -f1 | head -n $i | tail -n 1)
    b=$(grep -n "END OF GFF DUMP" texto.out | cut -d: -f1 | head -n $(( i - 1 )) | tail -n 1)
    echo "Começo do arquivo número $i -----------------------------------------------" > texto$i.out
    #tudo o que está entre `grep...` é apenas para dar o número da linha para o head -n. Assim o head vai pegar desde o início do arquivo até o final daquele bloco. No tail eu subtraio o "b" de "a" para conseguir pegar o bloco corretamente, excluindo o começo do arquivo, e salvo no arquivo texto$i.out
    head -n `grep -n "END OF GFF DUMP" texto.out | cut -d: -f1 | head -n $i | tail -n 1` texto.out | tail -n $(( a - b )) >> texto$i.out 2>&1
    echo "Final do arquivo número $i -----------------------------------------------" >> texto$i.out
    ((i=$i+1))
    done

    Muito obrigado pela ajuda e pelas dicas!! Forte abraço!
    luiz99 disse:

    pelo que esta pedindo muito provavelmente vc conseguiria fazê-lo com o sed, mas com a informação que vc passou eu nao tenho certeza, seria interessante vc postar algumas linhas do arquivo para vermos como filtrá-lo.

    vc quer tipo pegar as primeiras 100 linhas e jogar em outro arquivo ou pegar a primeira coluna das primeiras 100 linhas e jogar em outro arquivo?

    sempre quando vou tratar algum dado que não tem uma quantidade certa de linhas, arquivos etc eu dou um ls, cat etc para ler o conteudo e coloco o | wc -l para contar a quantidade de saidas, assim posso determinar quantos loops serão necessarios.

    LINHAS=$"0" # Determina o ponto de partida da contagem
    TOTAL_LINHAS=$(cat $ENDERECO_DO_ARQUIVO | wc -l) # identifica quantas linhas tem o arquivo, e consequentemente quantos loops terá o while
    while [ $LINHAS -ne $TOTAL_LINHAS ];do # a cada loop processa uma linha
    LINHAS=$[ $LINHAS + 1 ] # vai somando para não dar loop infinito

    # o codigo que faz a filtragem do texto

    done # fecha o while

    no codigo acima eu conseguiria ler apenas um arquivo, para ele ler todos de uma pasta vc deve fazer outro loop deixando este por dentro, e neste outro loop vc usaria a mesma logica para definir a quantidade de rodadas.

    se for o caso de ler linha por linha a cada loop deve se ler uma linha diferente então eu uso a variavel LINHAS que vai somando a cada loop, uso esta variavel no sed:
    LINHA_ ATUAL=$(cat $ENDERECO | sed -n ${LINHAS}p) # linha atual

    existe formas bem mais simples de se escrever estes loops, mas eu sei fazer assim.

  • Bacana seu script, eu não sei escrever desse jeito.

    eu faria algo semelhante só que escrevendo linha por linha e quando chegasse no "END OF GFF DUMP" eu mudaria de arquivo e continuaria esta escrita, o meu ficaria bem mais lento que o seu, parabéns!

    Qualquer coisa dá uma olhada no shell script do zero, mas acho que vc já não é mais iniciante
    http://www.mediafire.com/file/4x4yadaf8s9c7tx/Shell+Script+do+Zero.pdf
  • Olá amigos, movi a discussão para a sala Programação e Scripts por ser mais adequada ao conteúdo discutido.
    Abraços!
  • luiz99 disse:

    Bacana seu script, eu não sei escrever desse jeito.

    eu faria algo semelhante só que escrevendo linha por linha e quando chegasse no "END OF GFF DUMP" eu mudaria de arquivo e continuaria esta escrita, o meu ficaria bem mais lento que o seu, parabéns!

    Qualquer coisa dá uma olhada no shell script do zero, mas acho que vc já não é mais iniciante
    http://www.mediafire.com/file/4x4yadaf8s9c7tx/Shell+Script+do+Zero.pdf

    Obrigado pelas dicas!! Vou sim, olhar o arquivo e estudá-lo com mais calma agora. Valeu pela sugestão, abração!
Entre ou Registre-se para fazer um comentário.