Como posso apagar tudo entre dois marcadores em um arquivo?

4

Eu tenho um texto em um arquivo de texto, onde quero que tudo que esteja entre strings como \{{[} e {]}\} seja excluído - incluindo essas strings.  Essas duas cadeias podem estar em linhas diferentes, assim como na mesma linha. Em qualquer um dos casos, em a linha na qual o começo \{{[} está, não quero que o texto antes, ou seja, à esquerda, seja excluído - e o mesmo vale para o texto após {]}\} .

Aqui está um exemplo: Dado um arquivo de texto com o conteúdo

Bla Bla bla bla \{{[} more bla bla
even more bla bla bla bla. 

A lot of stuff might be here.

Bla bla {]}\} finally done.

Nonetheless, the \{{[} show {]}\} goes on.

o script deve retornar outro arquivo de texto com o conteúdo

Bla Bla bla bla  finally done.

Nonetheless, the  goes on.

Infelizmente, essa tarefa de aparência simples acabou sendo muito difícil para eu fazer com sed . Estou feliz com qualquer solução em qualquer idioma, desde que eu não precise instalar nada na minha máquina Linux padrão (C e algum java já está instalado).

    
por l7ll7 04.11.2015 / 17:42

3 respostas

5

com perl :

perl -0777 -pe 's/\Q\{{[}\E.*?\Q{]}\}\E//gs'

Observe que toda a entrada é carregada na memória antes de ser processada.

\Qsomething\E é para something a ser tratado como uma string literal e não como uma expressão regular.

Para modificar um arquivo regular no local, adicione a opção -i :

perl -0777 -i -pe 's/\Q\{{[}\E.*?\Q{]}\}\E//gs' file.txt

Com o GNU awk ou mawk :

awk -v 'RS=\\\{\{\[}|\{\]}\\}' -v ORS= NR%2

Lá, estamos definindo o separador de registro como um desses marcadores de início ou fim (somente gawk e mawk support RS sendo um regexp aqui). Mas precisamos escapar dos caracteres que são o operador regexp (barra invertida, { , [ ) e também a barra invertida mais uma vez porque é especial em argumentos para -v (usado para coisas como \n , \b . ..), daí as inúmeras barras invertidas.

Então, tudo o que precisamos fazer é imprimir todos os outros registros. NR%2 seria 1 (verdadeiro) para cada registro ímpar.

Para ambas as soluções, estamos assumindo que os marcadores são correspondidos e as seções não aninhadas.

Para modificar o arquivo no local, com versões recentes do GNU awk , adicione a opção -i inplace .

    
por 04.11.2015 / 18:17
3
sed   -e:t -e'y/\n/ /;/\{{\[}/!b'               \
      -e:N -e'/\{{\[.*{\]}\}/!N'               \
           -e's/\(\{{\[}\).*\n//;tN'          \
           -e'y/ /\n/;s/\{{\[}/& /;ts'          \
      -e:s -e's/\(\[} [^ ]*\)\({\]}\}\)/ /' \
      -ets -e's/..... [^ ]* .....//;s/ //g;bt'   \
<<""
#Bla Bla {]}\} bla bla \{{[} more bla bla
#even more bla bla bla bla. \{{[} 
#
#A lot of stuff might be here.
#hashes are for stupid syntax color only
#Bla bla {]}\} finally {]}\} done.
#
#Nonetheless, the \{{[} show {]}\} goes \{{[} show {]}\} on.
#Bla Bla {]}\} bla bla  finally {]}\} done.
#
#Nonetheless, the  goes  on.

Aqui está uma maneira muito melhor, no entanto. Muito menos substituições, e as que são feitas são para alguns caracteres de cada vez, em vez de .* o tempo todo. Praticamente a única vez em que .* é usado é limpar o espaço de padrão do espaço entre aspas quando a primeira ocorrência que ocorre for definitivamente emparelhada com a primeira extremidade seguinte. Todo o resto do tempo sed simplesmente D elimina o quanto precisa para chegar ao próximo delimitador que está ocorrendo. me ensinou isso.

sed -etD -e:t -e'/\{{\[}/!b'  \
    -e's//\n /;h;D'       -e:D \
    -e'/^}/{H;x;s/\n.*\n.//;}' \
    -ett    -e's/{\]}\}/\n}/' \
    -e'/\n/!{$!N;s//& /;}' -eD \
<<""
#Bla Bla {]}\} bla bla \{{[} more bla bla
#even more bla bla bla bla. \{{[} 
#
#A lot of stuff might be here.
#hashes are for stupid syntax color only
#Bla bla {]}\} finally {]}\} done.
#
#Nonetheless, the \{{[} show {]}\} goes \{{[} show {]}\} on.
#Bla Bla {]}\} bla bla  finally {]}\} done.
#
#Nonetheless, the  goes  on.

Os RHS \n ewline escapes talvez precisem ser substituídos por literais escapadas com novas linhas, embora.

Aqui está uma versão mais genérica:

#!/usr/bin/sed -f
####replace everything between START and END
   #branch to :Kil if a successful substitution
   #has already occurred. this can only happen
   #if pattern space has been Deleted earlier
    t Kil
   #set a Ret :label so we can come back here
   #when we've cleared a START -> END occurrence
   #and check for another if need be
    :Ret
   #if no START, don't
    /START/!b
   #sigh. there is one. get to work. replace it
   #with a newline followed by an S and save
   #a copy then Delete up to our S marker.
    s||\
S|
    h;D
   #set the :Kil label. we'll come back here from now
   #on until we've definitely got END at the head of
   #pattern space.
    :Kil
   #do we? 
    /^E/{
       #if so, we'll append it to our earlier save
       #and slice out everything between the two newlines
       #we've managed to insert at just the right points        
        H;x
        s|\nS.*\nE||
    }
   #if we did just clear START -> END we should
   #branch back to :Ret and look for another START
    t Ret
   #pattern space didnt start w/ END, but is there even
   #one at all? if so replace it w/ a newline followed
   #by an E so we'll recognize it at the next :Kil
    s|END|\
E|
   #if that last was successful we'll have a newline
   #but if not it means we need to get the next line
   #if the last line we've got unmatched pairs and are
   #currently in a delete cycle anyway, but maybe we
   #should print up to our START marker in that case?
    /\n/!{
       #i guess so. now that i'm thinking about it
       #we'll swap into hold space, and Print it
        ${  x;P;d
        }
       #get next input line and add S after the delimiting
       #newline because we're still in START state. Delete
       #will handle everything up to our marker before we
       #branch back to :Kil at the top of the script
        N
        s||&S|
    }
   #now Delete will slice everything from head of pattern space
   #to the first occurring newline and loop back to top of script.
   #because we've definitely made successful substitutions if we
   #have a newline at all we'll test true and branch to :Kil 
   #to go again until we've definitely got ^E
    D

... sem comentários ...

#!/usr/bin/sed -f
    t Kil
    :Ret
    /START/!b
    s||\
S|
    h;D
    :Kil
    /^E/{
        H;x
        s|\nS.*\nE||
    }
    t Ret
    s|END|\
E|
    /\n/!{
        ${  x;P;d
        }
        N
        s||&S|
    }
    D

Copiei a versão comentada para minha área de transferência e fiz:

{ xsel; echo; } >se.sed
chmod +x se.sed
./se.sed <se.sed
#!/usr/bin/sed -f
####replace everything between
   #branch to :Kil if a successful substitution
   #has already occurred. this can only happen
   #if pattern space has been Deleted earlier
    t Kil
   #set a Ret :label so we can come back here
   #when we've cleared a  occurrence
   #and check for another if need be
    :Ret
   #if no  at the head of
   #pattern space.
    :Kil
   #do we?
    /^E/{
       #if so, we'll append it to our earlier save
       #and slice out everything between the two newlines
       #we've managed to insert at just the right points
        H;x
        s|\nS.*\nE||
    }
   #if we did just clear  we should
   #branch back to :Ret and look for another , but is there even
   #one at all? if so replace it w/ a newline followed
   #by an E so we'll recognize it at the next :Kil
    s|END|\
E|
   #if that last was successful we'll have a newline
   #but if not it means we need to get the next line
   #if the last line we've got unmatched pairs and are
   #currently in a delete cycle anyway, but maybe we
   #should print up to our
    
por 04.11.2015 / 19:02
0

Se o seu arquivo é test.txt você pode usar:

sed ':a;N;$!ba;s/\n/ /g' test.txt|sed 's/\{{\[}.*{\]}\}//' 

o primeiro sed remove todas as novas linhas, o segundo remove o texto dentro das tags.

Eu não sei se você precisa de uma solução mais geral

    
por 04.11.2015 / 17:54