shell-script-pt
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [shell-script] Sed :: Não substituir entre dois intervalos.


From: Julio C. Neves
Subject: Re: [shell-script] Sed :: Não substituir entre dois intervalos.
Date: Mon, 15 Jul 2013 10:34:29 -0300

Há uns 10 dias estou escrevendo sobre isso, mas ainda está inacabado.
Pretendo que isso venha a substituir o capítulo sobre sed no meu livro.

Publico aqui o material, para submeter a críticas e correções dos colegas.
Por favor, não deixem de criticar e comentar e, se não for do interesse de
todos, favor fazê-lo em pvt.

Sed e etc...

Existe uma série de comando que não se encaixam nesses que já vimos. Vamos
então esmiuçar os principais para poder ampliar a utilidade do sed.
Gravando em outro arquivo

Usando *o *comando w (*write*) você também pode gravar a saída em um
arquivo especificado. Veja um comando para gravar os campos numéricos que
estão em um arquivo onde está tudo misturado, em um outro arquivo
especificado. Primeiramente veja o arquivo misto:

*$ cat misto*

123

234

acd

342

cdr

swax

Agora veja essa linha de comando:

*$ sed -n 's/^[[:alpha:]]*$/&/w alfa' misto*

Vamos entendê-la:

A opção -n é para que só imprima o que for ordenado;

[[:alpha:]] é uma lista composta somente pela classe POSIX [:alpha:] que
casa com qualquer letra e é o mesmo que [A-Za-z];

O asterísco (*) pega todas as ocorrências consecutivas de caracteres que
casam com esta lista;

O circunflexo (^) e o cifrão ($) marcam o início e o fim do texto,
respectivamente.

Assim essa primeira parte do comando de substituição pode ser lida como:
selecione as linha que entre seu começo e seu fim, tenham somente
caracteres alfabéticos.

A segunda parte do comando é um &, que, como você verá em detalhes logo aí
na frente (na seção "Guardando cadeias para uso posterior", neste mesmo
capítulo), recebe o texto da cadeia casada na primeira parte, isto é, os
textos dos campos inteiramente formados por caracteres alfabéticos.

Em seguida vem a *flag* /w que especifíca que a saída será gravada no
arquivo alfa.

Vamos ver se o comando funcionou:

*$ cat alfa*

acd

cdr

swax

Funfou de acordo com o que queríamos, mas não seria necessário usar o cat para
verificar o que foi gravado em alfa. Poderíamos ter gravado e mandado para
a tela usando o p que já vimos logo no início desse capítulo, veja:

*$ sed -n 's/^[[:alpha:]]*$/&/pw alfa' misto*

acd

cdr

swax

Dessa forma, ao mesmo tempo que gravamos o arquivo alfa, também jogamos a
saída para a tela, como faria um comando tee. É muito frequente vermos a
opção -n e o p caminhando/atuando juntos.
Inserindo dados de outro arquivo

Acabamos de mostrar como gravar dados em um arquivo, mostramos antes como
inserir linhas com os comandos a e i, e agora mostraremos como inserir um
arquivo inteiro em determinado ponto usando o comando r ARQUIVO (*read*).
Veja o exemplo:

*$ sed '$ r alfa' misto*

123

234

acd

342

cdr

swax *Fim do arquivo misto*

1b2c3 *Início do arquivo alfa*

acd

cdr

swax
Numerando linhas

O sinal de igual (=) serve para informar o número de uma determinada linha.
por exemplo, se quisermos saber as linhas que possuem a cadeia UNIX
emquequeisso, basta
fazer:

*$ sed '/UNIX/=' quequeisso*

ATENCAO, O TEXTO ABAIXO NAO EH TREINAMENTO,

EH UMA LAVAGEM CEREBRAL!!!

3 *A próxima linha tem UNIX*

O Shell alem de analisar cada dado entrado a partir do prompt do UNIX,

interfaceando com os usuarios, tem tambem as seguintes atribuicoes:

Interpretador de comandos;

6 *A próxima linha tem UNIX*

Controle do ambiente UNIX;

Redirecionamento de entrada e saida;

Substituicao de nomes de arquivos;

Concatenacao de pipe;

Execucao de programas;

Poderosa linguagem de programacao.

Não era bem isso que eu queria. Como bastava os números das linhas, sem
necessitar listar o arquivo inteiro, deveria ter usado a opção -n. Vejamos:

*$ sed -n '/UNIX/=' quequeisso*

3

6

Para saber quantas linhas tem um arquivo, basta imprimir o número da
última, desta forma:

*$ sed -n '$ =' quequeisso*

11
A opção -r

Se eu tenho uma data no formato dia/mês/ano, ou seja dd/mm/aaaa e desejo
passá-la para o formato aaaa/mm/dd, eu deveria fazer:

*$ sed 's/^\([0-9]\{2\}\)\/\([0-9]\{2\}\)\/\([0-9]\{4\}\)$/\3\/\2\/\1/' <<<
31/12/2009*

2009/12/31

Funcionou, mas a legibilidade disso está um horror! Aí tem mais
contrabarra(\) que
qualquer outra coisa!

Justamente para facilitar a montagem das *Expressões Regulares* e sua
legibilidade é que o GNU-sed tem a opção -r. Ela avisa ao sed que serão
usados *metacaracteres* avançados, que desta forma se encarrega das suas
interpretações, não deixando-os para a interpretação do *Shell*.

Esse mesmo sed poderia (e deveria) ser escrito da seguinte forma:

*$ sed -r 's/^([0-9]{2})\/([0-9]{2})\/([0-9]{4})$/\3\/\2\/\1/' <<<
31/12/2009*

2009/12/31

Melhorou mas ainda não está bom, porque ainda existem contrabarras (\)
"escapando"
as barras (/) da data para que o sed não as confunda com as barras (/)
separadoras
de campo. Para evitar esta confusão, basta usar outro caractere, digamos o
hífen (-), como separador, Vejamos como ficaria:

*$ sed -r 's-^([0-9]{2})/([0-9]{2})/([0-9]{4})$-\3/\2/\1-' <<< 31/12/2009*

2009/12/31

Agora ficou mais fácil de ler e podemos decompor a *Expressão Regular* para
destrinchá-la. Vamos lá:

1ª Parte - ^([0-9]{2})/([0-9]{2})/([0-9]{4})$. Vamos dividi-la:

^([0-9]{2}) - A partir do início (^) procuramos dois ({2})
algarismos([0-9]). Isso
casa com o dia;

/([0-9]{2})/ - Idêntica à anterior, porém por estar entre barras (/), casará
com o mês;

([0-9]{4})$ - procuramos quatro ({4}) algarismos ([0-9]) no fim ($). Isso
casa com o ano;

2ª Parte - \3/\2/\1 Repare que as pesquisas de dia, mês e ano foram
colocados entre parênteses. Como os *textos* casados pelas *Expressões
Regulares* no interior dos parênteses são guardados para uso futuro, os
retrovisores \3,\2 e \1 foram usados para recuperar ano, o mês e o dia,
respectivamente.

Da forma que colocamos até agora, parece que a opção -e deverá ser usada
somente em confunto com grupos e retrovisores, mas isso não é verdade.
As *Expressões
Regulares* que usam + e {}, também devem ter esses carateres precedidos por
uma contrabarra (\) ou o sed deve ser feito com a opção -e.

 ATENÇÃO! A opção -r só existe no GNU sed. Quando estivermos usando UNIX,
eses caracteres que acabamos de citar, devem ser protegidos por uma
contrabarra (\).

Também é comum ouvirmos (e citarei algumas vezes ao longo deste livro)
"escapados (de *escape*) por uma contrabarra".

A opção -i

Se você deseja editar via sed e definitivamente alterar um arquivo, você
deve fazer algo assim:

*$ sed 's/Texto/TextoAlterado/' arquivo > arquivo.alterado*

*$ mv arquivo.alterado arquivo*

Porém, no GNU sed (sempre o GNU facilitando nossa vida) você poderia fazer
isso de forma muito simplificada usando a opção -i e este mesmo exemplo
ficaria da seguinte maneira:

*$ sed 's/Texto/TextoAlterado/' arquivo*

E dessa forma o conteúdo do próprio arquivo seria atualizado nele mesmo.

Suponha que queira trocar todos os artigos os do nosso amigo quequeisso pelo
seu similar em inglês the. Então cheio de convicção, faço:

*$ sed -i 's/os/the/g' quequeisso*

e para verificar:

*$ cat quequeisso*

ATENCAO, O TEXTO ABAIXO NAO EH TREINAMENTO,

EH UMA LAVAGEM CEREBRAL!!!

O Shell alem de analisar cada dado entrado a partir do prompt do UNIX,

interfaceando com the usuarithe, tem tambem as seguintes atribuicoes:

Interpretador de comandthe;

Controle do ambiente UNIX;

Redirecionamento de entrada e saída;

Substituicao de nomes de arquivthe;

Concatenacao de pipe;

Execucao de programas;

Poderthea linguagem de programacao.

Xiii, lambuzei o quequeisso porque eu deveria ter especificado que as
cadeias os estariam entre espaços. Então vamos devolvê-lo à sua forma
anterior:

*$ sed -i 's/the/os/g' quequeisso*

*$ cat quequeisso*

ATENCAO, O TEXTO ABAIXO NAO EH TREINAMENTO,

EH UMA LAVAGEM CEREBRAL!!!

O Shell alem de analisar cada dado entrado a partir do prompt do UNIX,

interfaceando com os usuarios, tem tambem as seguintes atribuicoes:

Interpretador de comandos;

Controle do ambiente UNIX;

Redirecionamento de entrada e saída;

Substituicao de nomes de arquivos;

Concatenacao de pipe;

Execucao de programas;

Poderosa linguagem de programacao.

Ainda bem que funcionou. Se anteriormente o texto tivesse uma ou mais
cadeia(s) the, essa volta não seria tão fácil. E é por isso que a opção -i tem
um facilitador incrível que permite especificar o nome de um arquivo que
manterá o conteúdo anterior intacto, para o caso de necessitar uma
recuperação. Já que um bom exemplo vale mais que mil palavras, veja o caso
abaixo:

*$ sed -i.veio 's/ os / the /g' quequeisso*

*$ ls queque**

quequeisso quequeisso.veio

Epa, agora são dois arquivos. Vamos ver seus conteúdos:

*$ cat quequeisso*

ATENCAO, O TEXTO ABAIXO NAO EH TREINAMENTO,

EH UMA LAVAGEM CEREBRAL!!!

O Shell alem de analisar cada dado entrado a partir do prompt do UNIX,

interfaceando com the usuarios, tem tambem as seguintes atribuicoes:

Interpretador de comandos;

Controle do ambiente UNIX;

Redirecionamento de entrada e saída;

Substituicao de nomes de arquivos;

Concatenacao de pipe;

Execucao de programas;

Poderosa linguagem de programacao.

*$ cat quequeisso.veio*

ATENCAO, O TEXTO ABAIXO NAO EH TREINAMENTO,

EH UMA LAVAGEM CEREBRAL!!!

O Shell alem de analisar cada dado entrado a partir do prompt do UNIX,

interfaceando com os usuarios, tem tambem as seguintes atribuicoes:

Interpretador de comandos;

Controle do ambiente UNIX;

Redirecionamento de entrada e saída;

Substituicao de nomes de arquivos;

Concatenacao de pipe;

Execucao de programas;

Poderosa linguagem de programacao.

Como vocês viram o quequeisso foi alterado, porém a opção -i usada
juntamente com a extensão .veio, salva uma cópia integra em quequeisso.veio.
Repito: caso a opção -i tivesse sido usada sem a extensão. Os dados teriam
sido gravados no próprio quequeisso.
A opção -f

Vamos pegar um pedaço de /etc/passwd e colocá-lo no arquivo passwd.tmp:

*$ tail -7 /etc/passwd > passwd.tmp*

Veja com ele ficou (os que terminam em reticências foram cortados porque
são dados que não interessam ao caso e para não haver quebra de linha que
prejudica a leitura):

*$ cat passwd.tmp*

bin:x:2:2:bin:/bin:/bin/sh

pulse:x:109:114:PulseAudio daemon,,,:/var/run/pulse:...

rtkit:x:110:117:RealtimeKit,,,:/proc:/bin/false

saned:x:111:118::/home/saned:/bin/false

hplip:x:112:7:HPLIP system user,,,:/var/run/hplip:...

gdm:x:113:120:Gnome Display Manager:/var/lib/gdm:...

julio:x:1000:1000:Julio Neves,,,:/home/julio:/bin/bash

postfix:x:114:123::/var/spool/postfix:/bin/false

Tenho um outro arquivo com *logins names* que desejo excluir de passwd.tmp.

*$ cat exclui*

postfix

hplip

gdm

bin

pulse

Eu posso, usando a opção -f ARQUIVO do sed, excluir direto de passwd.tmp os
registros dos usuários indicados em exclui, mas antes preciso prepará-lo
para que ele, que agora só tem os nomes dos usuários, passa a ter o comando
de deleção do sed, no formato:

/^USUARIO:/d

Onde as barras (/) e o d formam a sintaxe do comando e o circunflexo
(*Expressão
Regular* que indica inicio de linha) e os dois pontos (:) marcam os limites
da palavra. Se esses limites não fossem estipulados, todos os
registros depasswd.tmp que
contivessem a palavra bin - mesmo como parte de outra maior - seriam
deletados).

Preparando exclui:

*$ sed -i 's|^|/^|;s|$|:/d|' exclui*

*$ cat exclui*

/^postfix:/d

/^hplip:/d

/^gdm:/d

/^bin:/d

/^pulse:/d

Como você notou, usamos a barra vertical (|) como delimitador e o ponto e
vírgula (;) serviu para usarmos dois comandos *substitute* (s) dentro do
mesmo sed.

Agora que todos os comandos já estão montados, podemos proceder à deleção.
Veja como:

*$ sed -i -f exclui passwd.tmp*

*$ cat passwd.tmp*

rtkit:x:110:117:RealtimeKit,,,:/proc:/bin/false

saned:x:111:118::/home/saned:/bin/false

julio:x:1000:1000:Julio Neves,,,:/home/julio:/bin/bash

Isto é a opção -f informou o nome do arquivo de comandos para o sed
(exclui) utilizar e em seguida, informamos o arquivo a ser
alterado(passwd.tmp). Como
usamos a opção -i, as alterações foram feitas diretamente no último.
Evitando o pipe

Como já vimos, o sed, assim como o *Shell*, usa o ponto e vírgula para
separar comandos, desta forma evitando construções do tipo:

*sed ... | sed ... ... | sed ...*

Que fatalmente teria sua execução mais lenta.

Todavia, quando se deseja colocar no mesmo sed diversos comandos, a
legibilidade começa a ficar prejudicada, no entanto, esta instrução
comporta outro mecanismo, que melhora – e muito – a legibilidade de sed multi
comandos. Veja um exemplo para remover linhas de comentário (que tenham um #),
linhas vazias e linhas só com <TABs> e/ou espaços em branco.

Primeiramente vamos ver o arquivo testa_sed:

*$ cat -net testa_sed*

 1 1 # Esta linha nao serah analisada$

 2 # A partir daqui, existem linhas vazias$

 3 #+ linhas somente com espacos em branco$

 4 #+ e linhas com <TAB>$

 5 $

 6 2 # Esta eh uma linha que deve ser mantida$

 7 $

 8 3 # Esta tb deve ser mantida$

 9 ^I # Isto eh um <TAB>$

 10 4 # Essa tb serah mantida$

Só para você entender este cat maluco, nele usei as seguintes opções:

-n Numera à esquerda cada uma das linhas;

-e Exibe o final de linha como um cifrão ($);

-t Exibe os caracteres de controle (o <TAB> é um ^I).

Repare que a linha 5 está vazia, a 7 só tem espaços em branco e a 9 tem um<TAB>.

Agora vejamos a linha de comando:

*$ sed '*

*2,$ {*

*s/#.*//*

*s/[[:blank:]]*$//*

*/^$/ d*

*!d*

*}' testa_sed*

1 # Esta linha nao serah analisada

2

3

4

Este sed, atuará a partir da linha 2 até o final do arquivo testa_sed
(2,$). As chaves foram colocadas para formar um bloco de comandos sobre o
qual o sed atuará. Vejamos:

s/#.*// - Apaga tudo que vem após um comentário (#), inclusive ele;

s/[[:blank:]]*$// - [:blank:] é uma classe POSIX que engloba <TAB> e espaço
em branco. Dessa forma, essa linha apaga <TABs> e brancos do final de cada
linha;

/^$/ d - Deleta linhas sem conteúdo. assim sendo se as duas linhas
anteriores apagaram a linha inteira, isto é, linhas toda de comentários ou
linhas que só contivessem brancos e <TABs>;

!d - Só para gerar a listagem do que sobrou (se usasse o comando p, da
linha 2 até a última, viriam todas duplicadas).
Sed multi linha

Aqui, o termo multi linha não significa que o sed se expande por mais de
uma linha, até porque já vimos este comando com mais de uma linha de código
e o uso de ponto e vírgula (;) para colocar mais de um comando de sed na
mesma linha. O que queremos mostrar a partir desse ponto é como conseguimos
trabalhar simultaneamente com mais de uma linha do arquivo de entrada.

Para início de conversa, precisamos entender dois conceitos fundamentais:
Pattern Space (ou Pattern Buffer)

O sed lê linha a linha e o registro que está sendo lido é colocada em um *
buffer* chamado *Pattern* *Space.* É neste *buffer* que ele trabalha a
linha que acabou de ser lida é daí que, após ser processada, ela é mandada
para a saída. Quando usamos um sed para processar mais de uma linha
simultaneamente, temos que colocar todas juntas neste local.
Hold Space (ou Hold Buffer)

Como já diz o nome, *Hold Space* é usado para salvar parte ou todo o *
Pattern* *Space*, é como um armazenamento de longo prazo, de modo que você
pode pegar algo, armazená-lo e reutilizá-lo mais tarde, quanto o sed estiver
processando outra linha. Você não processa diretamente o *Hold Space*, você
precisa copiá-lo do ou acrescentá-lo ao *Pattern* *Space,* se você quiser
fazer alguma coisa com ele.

Eu já deveria ter mencionado a função do *Pattern* *Space* antes, porque
sempre que se usa um sed, se usa o *Pattern* *Space,* veja só a a sequência
de como as coisas acontecem durante a execução de um comando sed:

   -

   O sed lê linha a linha;
   -

   Retira o *line* *feed* (<ENTER>) do final do registro;
   -

   Coloca o registro lido no *Pattern* *Space* (sem o *line* *feed);*
   -

   Processa o comando do sed para aquela linha;
   -

   Imprime o resultado a partir do *Pattern* *Space.*

Como as linhas são processadas a partir do Pattern Space e lá elas estão
sem o <ENTER> final, nada acontecerá se você fizer:

*$ sed 's/\n//' ARQUIVO*

Pois o <ENTER> (\n) não casará com nada.

Só expliquei seu funcionamento agora, porque somente a partir deste ponto
precisaremos entender isso.
Comandos que interagem com os buffers

Existem oito comandos - cuja funções estão sintetizadas na tabela a seguir
- para o sed interagir com esses dois *buffers*:

*Comandos*

*Função*

n N

Lê/adiciona a próxima linha ao *Pattern Space*.

h H

Copia/adiciona *Pattern Space* ao *Hold Space*.

g G

Copia/adiciona *Hold Space* ao *Pattern Space*.

p P

Imprime todas/1ª linha do *P**attern **S**pace*

d...D

Deleta todas/1ª linha do *P**attern **S**pace*

x

Intercambia o conteúdo dos dois *buffers*.

Na tabela que acabamos de ver, "copia" significa colocar um conteúdo no *
buffer*, destruindo o que já havia e "adiciona", significa somar o dado ao
que já existia no *buffer,* separando o conteúdo antigo do recente por
um<ENTER> (\n).

Ainda sobre essa tabela, os comandos p e d, são os mesmos que já vimos, que
servem para listar e deletar, respectivamente, o conteúdo do *Pattern* *
Space.*

Como tive muita dificuldade para entender isso, vou explicar bem devagar:

O comando n imprime todo o conteúdo do *Pattern* *Space* (a não ser que a
opção -n do sed esteja ativa), esvazia totalmente o *Pattern* *Space* e lê
o próximo registro.

O comando N não lista nem esvazia o *Pattern* *Space*, ele manda ler mais
uma linha e a coloca por baixo do que já existia no *Pattern* *Space*,
separando-a do que lá estava por um caractere de *line* *feed* (<ENTER>).

O comando p, é nosso velho conhecido e ele lista todo o conteúdo do *Pattern
* *Space*., já o comando P, lista somente até o primeiro *line* *feed*
(<ENTER>), isto é, lista somente a primeira linha do *Pattern* *Space*.
Nenhum dos dois altera o conteúdo do *Pattern* *Space*.

Vamos ver exemplos:

*$ seq 6 | sed -n 'N;P'*

1

3

5

*$ seq 6 | sed -n 'N;p'*

1

2

3

4

5

6

No primeiro exemplo listei somente os ímpares, porque enquanto o N enfiava
a linha par por baixo da ímpar, o P imprimia somente a primeira. No segundo
o p imprimia todo o conteúdo, então listou toda a entrada:

*$ seq 6 | sed -n 'n;P'*

2

4

6

*$ seq 6 | sed -n 'n;p'*

2

4

6

Nesse caso a saída dos 2 exemplos é idêntica, porque o comando n é
destrutivo. ele apagou o *buffer* (que no início tinha o número 1) e jogou
outro por cima (no caso o 2). Como tinha uma única linha no *Pattern* *Space
*, tanto faz usar o p ou o P.

O comando d apaga o conteúdo do *Pattern* *Space*, lê a próxima linha e sai
do comando, voltando para o sed.

O comando D remove somente a primeira linha do *Pattern* *Space*, e volta
para o primeiro comando do sed.

Para eliminar a linha que tem a cadeia 3:

*$ seq 6 | sed '/3/{d;n}'*

1

2

4

5

6

Ou seja, quando encontrou a cadeia 3, o comando d mandou esvaziar o *Pattern
* *Space* mandando seu conteúdo (o 3) pro brejo e o n leu o próximo
registro.

Para deletar 3 linhas a partir da linha 2, podemos fazer:

*$ seq 6 | sed '2 { Posiciona na linha 2*

*> N Lê a 3 para o Pattern Space*

*> N Lê a 4 para o Pattern Space*

*> d Limpa o Pattern Space*

*> }'*

1

5

6

Os comentários laterais já explicaram esse exemplo.

Como já vimos, o arquivo misto tem sete linhas, se quisermos pegá-las de
duas em duas, podemos fazer:

*$ sed -e '{*

*> N **Adicionou a linha ao Pattern Space*

*> s/\n/\t/ **Agora já tem um <ENTER> para ser trocado*

*> }' misto*

123 234

acd 342

cdr swax

1b2c3

A explicação para isso é que o sed lê a primeira linha e a coloca no *P**
attern* *S**pace,* o comando N *(Next)* lê a próxima linha e a coloca
também no *P**attern* *S**pace.* Nesse momento o que temos lá são dois
registros do arquivo misto, separados por um fim de linha (\n). Então o
comando s *(substitute)* troca o fim de linha (\n) por um <TAB> (\t).

Vamos numerar as linhas de frutas:

*$ sed '=' frutas *

1

abacate

2

maçã

3

morango

4

pera

5

tangerina

6

uva

Agora vamos botar o número junto à linha, separados por um <TAB>.

*$ sed = frutas | sed 'N;s/\n/\t/' *

1 abacate

2 maçã

3 morango

4 pera

5 tangerina

6 uva

Ou seja o N trouxe outra linha para o *Space* *Buffer* e o comando s (*
substitute*) trocou o <ENTER> (\n) pelo <TAB> (\t).

Olha essa linha de comando para imprimir somente uma, de duas ou mais linhas
consecutivas e iguais:

*sed '$!N; /^\(.*\)\n\1$/!P; D' ARQUIVO*

Para entender vamos dividi-lo:

$!N Se for a última linha, não adicione ao *Pattern* *Space;*

/^\(.*\)\n\1$/ Aqui vamos dividir a explicação novamente:

^\(.*\)\n – Forma um grupo com o que está desde o início (^) até o \n (que
separa as duas linhas no *Pattern* *Space*);

\1$ - O retrovisor que guardou o texto casado no grupo;

Desta forma, a *Expressão Regular* como um todo procurou no *Pattern* *Space
* um texto, um \n e o mesmo texto, isto é, um texto repetido;

!P Neste caso, não imprima;

D Delete a primeira linha!

Vamos vê-lo atuando:

*$ sed '$!N; /^\(.*\)\n\1$/!P; D' <<< '11111*

*> 22222*

*> 22222*

*> 33333*

*> 33333*

*> 33333'*

11111

22222

33333

Que, para facilitar a legibilidade, também poderia ter sido escrito
colocando cada um dos três comandos em uma linha e usando a opção -r
(para *Expressões
Regulares* estendidas como os parênteses) para evitar as contrabarras,
ficando da seguinte forma:

*$ sed -r '{*

*> $!N*

*> /^(.*)\n\1$/!P*

*> D*

*> }' <<< '11111*

*> 22222*

*> 22222*

*> 33333*

*> 33333*

*> 33333'*

Alterando o fluxo do programa

Lá no início deste capítulo, dissemos que o sed, por ser uma linguagem de
programação completa, possui comandos para controle e desvio de fluxo.
Vamos ver como fazer isso.

Muitas vezes somos obrigados a criar um desvio (em inglês *branch*) que
ocorrerá dependendo de uma condição e outras vezes precisamos de um desvio
incondicional, em que o fluxo do programa é sempre desviado.

O primeiro que citamos, o condicional, é chamado pelo comando t e desviará
para um ponto do sed caso a última substituição (s/.../.../) tenha sido bem
executada;

O segundo, o incondicional, é chamado pelo comando b e sempre desviará para
um outro ponto do *script**. *
Especificando rótulos (labels)

Para mudar o fluxo de execução do sed, você precisa especificar o endereço
para o qual haverá o desvio e rotular esse local. Caso este rótulo não
tenha sido definido, o desvio se dará para o final do *script*. Para criar
um rótulo, basta preceder com um dois pontos (:) o nome que você escolheu e
colocá-lo no endereço para o qual você deseja que o programa vá.

*:ROTULO*
Testando condições

Veja esse exemplo, onde desejamos trocar todas as letras minúsculas que
ocorram antes de um hífen (-) por um algarismo 0.

*$ sed -r '{*

*> :volta*

*> s/^([^-]*)[a-z]/\10/*

*> t volta*

*> }' <<< abc9Aaa-a111a *

0009A00-a111a

A primeira linha do *script* é o rótulo :volta, a segunda linha troca (s) a
partir do início (^), tudo que não é hífen (([^⁻]*)) e tenha uma letra
minúscula ([a-z]) após. Isso será trocado pelo valor do retrovisor (\1) cujo
texto foi gerado pelo grupo definido pelos parênteses, seguido por um zero.
O comando t da terceira linha, diz para o fluxo voltar para o rótulo
:volta caso
a substituição tenha sido bem sucedida, pois isto é sinal que ainda pode
haver mais letra(s) para ser(em) substituída(s). Como houve um
retorno, osed não
será completado enquanto estiver achando letras e por isso não lerá outro
registro, isto é, formamos um *loop* para cada linha lida que tenha
letra(s) antes do um hífen (-). Caso a linha não tenha hífen (-), todas as
letras serão trocadas por zeros.

Uma coisa interessante é que nesse caso, a execução será feita de traz para
a frente e, para você ver todos os passos do *loop* sobre a mesma linha,
vamos inserir o comando P. Veja:

*$ sed -r '{*

*> :volta*

*> P*

*> s/^([^-]*)[a-z]/\10/*

*> t volta*

*> }' <<< abc9Aaa-a111a *

abc9Aaa-a111a

abc9Aa0-a111a

abc9A00-a111a

ab09A00-a111a

a009A00-a111a

0009A00-a111a

0009A00-a111a
Desvio incondicional

No exemplo a seguir, tirado de
http://www.grymoire.com/Unix/Sed.html#uh-58(Valew!), veremos alguns
desvios incondicionais, que acarretam mudanças no
fluxo de execução do *script*.

Ele serve listar todos os parágrafos que contenham uma determinada cadeia
que estão em um arquivo especificado. Para isso partimos dos seguintes
pressupostos:

   -

   Os parágrafos são separados entre si por linhas vazias (em branco);
   -

   O arquivo a ser examinado e a cadeia e ser pesquisada, serão passados
   como parâmetro para o programa nesta ordem.

Para facilitar a explicação, cada linha do código será numerada (cat -n).
Veja:

*$ cat -n testa_branch.sh*

 1 #!/bin/sh

 2 sed -n "

 3 # Se linha vazia, vá pesquisar cadeia

 4 /^$/ b PesqCad

 5 # Senão, acrescenta a linha ao hold buffer

 6 H

 7 # Fim de arquivo também é fim de parágrafo

 8 $ b PesqCad

 9 # Vai para o fim para saltar PesqCad

 10 b

 11 # Aqui que vamos procurar a cadeia no parágrafo

 12 :PesqCad

 13 # Manda todo o parágrafo para o pattern space

 14 x

 15 # Procura a cadeia, se achar imprime

 16 /$1/ p

 17 " $2

*Linha*

*Para fazer...*

4

/^$/ pesquisa linha vazia e quando a acha, vai para (b) PesqCad para
pesquisar a cadeia no arquivo

6

Se não foi para PesqCad, acrescenta esta linha ao *Hold* *Space* (H) para
nele montar o parágrafo

8

Caso encontre o fim ($), do arquivo, que também é fim de parágrafo,
vai para(b) a
rotina PesqCad

10

Como a seguir vem a rotina PesqCad, esse desvio (b) vai para o fim (porque
está sem rótulo) do sed

12

Rótulo da rotina

14

Manda o conteúdo do *Hold* *Space* para o *Pattern* *Space* (x) para ali
poder trabalhar este parágrafo

16

Pesquisa a cadeia passada como parâmetro (/$1/) e, caso exista, imprime
todo o conteúdo do *buffer*

17

Arquivo

*Especificando rótulos (labels)*

Para mudar o fluxo de execução do sed, você precisa especificar o endereço
para o qual haverá o desvio e rotular esse local. Para fazer isso, basta
preceder com um dois pontos (:) o nome que você escolheu e colocá-lo no
endereço para o qual você deseja que o programa vá.

:ROTULO
Testando condições

Para testar condições use o comando t ROTULO, caso




Abcs,
Julio
*@juliobash
*



Em 13 de julho de 2013 06:31, Rodrigo Boechat <
address@hidden> escreveu:

> **
>
>
> Pessoal, bom dia!
>
> Como eu faria para não aplicar substituições entre dois intervalos?
>
> Exemplo:
>
> cat >01<<EOF
> qwerty
> qwerty
> #f1#qwerty
> qwerty#ff1#
> qwerty
> #f2#qwerty#ff2#
> qwerty
> qwerty
> qwerty
> EOF
>
> Tentando da forma que descrevo abaixo, obtive o seguinte erro:
> sed: -e expressão #1, caractere 29: comando desconhecido: `|'
>
> sed '
> /^\#f1\#/,/^\#ff1\#/ | /^\#f2\#/,/^\#ff2\#/ ! {
> s/qwerty/TESTE/g
> }' 01
>
> Andei pesquisando na net e ainda não achei algo parecido.
> Preciso encontrar um intervalo, #f1# e#ff1#, ou o outro, #f2# e #ff2#,
> para não executar o s/qwerty/TESTE/g, dentro deles.
>
> Alguém sabe como me ajudar?
>
> Desde jáagradeço!
>
> Rodrigo Boechat
>
> [As partes desta mensagem que não continham texto foram removidas]
>
>  
>


[As partes desta mensagem que não continham texto foram removidas]



reply via email to

[Prev in Thread] Current Thread [Next in Thread]