Dominando o SED

Wikisource, a biblioteca livre

Wikitext.svg
Este texto precisa ser wikificado. Por favor, formate esta página baseando-se nas diretrizes estabelecidas no livro de estilo. Remova este aviso somente depois de todo o texto estar wikificado.

Índice

[editar] Nota do autor

Escrevi este livro em 2002, entre os meses de Março e Setembro. É uma obra inacabada, que contém cerca de 60% do conteúdo planejado. Após este período a escrita parou e eu não tive mais vontade de continuar. A idéia era fazer um guia completo sobre o sed, uma bíblia que abrangesse todos os tópicos, dando dicas avançadas.

Hoje (Novembro de 2005) estou publicando o livro na Internet, com o texto intocado, como parou em 2002. Considero este um trabalho que não deve ficar restrito ao meu computador pessoal, pois mesmo incompleto pode ajudar outras pessoas a dominarem o assunto.

Se você é novato em sed, leia o sed­HOWTO[(http://aurelio.net/sed/sed­HOW]TO) primeiro. Terminada a leitura deste livro, domine também as Expressões Regulares (http://aurelio.net/er) para obter o máximo do sed.

DIREITOS AUTORAIS (COPYRIGHT): Este conteúdo é livre, você pode reproduzi­lo em qualquer meio, mas sempre deve citar a autoria (Aurélio Marinho Jargas) e o endereço oficial http://aurelio.net/sed/livro. Boa leitura!

[editar] Instalação

Antes de mais nada, o site http://sed.sf.net é a fonte de informação mais atualizada sobre onde encontrar o sed para as várias plataformas suportadas. Consulte-­o!


[editar] UNIX/Linux/Mac OS X

Espera aí, você vai me dizer que tem um UNIX/Linux e não tem sed instalado nele? Conta outra! &:D

Para atualizar uma versão existente, o procedimento é o mesmo do de outros programas, e isso pode requerer a instalação de um RPM, um pacote .deb ou baixar um .tar.gz e compilá­-lo.


[editar] Windows/DOS

Usuários de Windows têm duas opções para usar o sed:

  • SED.EXE no DOS
  • sed no Cygwin

O sed nasceu no UNIX, mas várias versões do programa foram feitas para MS­DOS devido à falta de uma ferramenta semelhante nesse sistema. O nome genérico é SED.EXE, mas também pode ser encontrado como SED.ZIP, caso acompanhe documentação.

A instalação não tem segredo. Basta copiar o arquivo SED.EXE para um diretório que esteja no PATH do seu MS­DOS. Lugares comuns são C:\Windows, C:\WIN ou C:\WINNT.

Para quem gosta de UNIX/Linux, é aconselhável instalar o Cygwin (http://aurelio.net/cygwin), uma solução completa com ferramentas UNIX que roda no Windows. Com um clique, você tem acesso a uma janelinha preta com sed, bash, cat, tr, grep, find, vi e dezenas de outros programas. Vale conferir!


[editar] Endereço

Prepare-­se. Respire fundo. Abra sua mente para receber conceitos novos, empolgantes e poderosos!

Para começar, deixemos claro que endereço não é um comando, mas sim o seu contexto. É o elemento que diz exatamente em quais linhas um comando, ou bloco deles, deverá ser aplicado.

O QUE FAZER ­­­> comando

ONDE FAZER ­­­> endereço

O endereço é algo simples se seus problemas forem simples. Mas quando se quer resolver algo realmente cabeludo com sed, o endereço também pode se tornar um monstro, daqueles que, de tão feios, quem olha pensa: "Tomara que eu nunca precise fazer manutenção nisso".

Para facilitar, podemos fazer uma analogia com os endereços de nossas casas, compostos por: nome da rua, número, cidade e CEP. O comando sed seria o carteiro que, baseado no endereço escrito na carta, deve encontrar o local correto.

Sabemos que quanto mais detalharmos o endereço, colocando todos os dados corretamente, mais fácil é para o carteiro encontrar o destino de nossa carta. Porém, na falta do CEP ou do número da casa, a carta não chegará ao seu destino por causa do endereço incorreto.

No sed é assim que acontece. Um endereço correto é indispensável para que o comando seja aplicado no lugar certo. Assim, torna­se essencial para o programador, ao escrever um comando sed, especificar o endereço com exatidão e sem ambigüidades.

O domínio do endereço diferencia os gurus dos gafanhotos

Façamos um mergulho profundo no assunto para dissecar todas as formas de utilização e conhecer as soluções para os problemas mais rotineiros.

Há três tipos de endereços:

  1. Endereço pelo número da linha
  2. Endereço pelo conteúdo da linha
  3. Endereço múltiplo, que abrange mais de uma linha


[editar] Um endereço sempre aponta para uma linha inteira

A primeira coisa que deve ser assimilada sobre o conceito de endereço é que ele sempre referencia uma linha inteira, não somente parte dela. Afinal, sendo o sed um editor orientado à linha, o endereço não poderia ser diferente.

Então, sempre que pensar em endereços, pense em linhas. Linhas inteiras.

Pode-­se redefinir os três tipos de endereço e ilustrar como cada um deles é interpretado pelo sed:

  1. "Quero a linha número N"
  2. "Quero a linha que contém a palavra ABCDE"
  3. "Quero todas as linhas que estejam entre essas duas linhas"


[editar] Como endereçar pelo número da linha

A maneira mais simples de se determinar um endereço é indicar diretamente sua posição no arquivo, informando ao sed o número da linha à qual se quer aplicar o comando. Por exemplo, para apagar a 5ª linha de um arquivo:

  • prompt$ sed '5 d' arquivo

Fácil, não? Observe que o espaço em branco entre o endereço e o comando é opcional, então também poderia ser 5d. Mas para facilitar a visualização de quem é quem, vamos separá­los sempre.

E assim se endereça, numericamente, qualquer comando à linha desejada, desde que se saiba qual é sua posição no arquivo.


[editar] Como endereçar a primeira e/ou a última linha

Para endereçar a primeira linha, é barbada: 1. Nada mais. Vamos apagá-­la?

  • prompt$ sed '1 d' arquivo

Moleza. Mas e a última? Nem sempre sabemos exatamente quantas linhas tem o arquivo, e é incômodo ter que usar outro programa para fazer isso antes de passarmos esse número ao sed.

Para resolver esse problema, temos um caractere especial de endereço: o cifrão $, que representa a posição da última linha do arquivo. Então é fácil apagar a última linha:

  • prompt$ sed '$ d' arquivo

Note que em sistemas UNIX o uso das aspas simples é obrigatório. Sem elas o shell tentaria expandir a variável $d e o sed receberia um comando vazio.


[editar] Como endereçar a linha que contém determinada palavra

Embora seja prático e fácil endereçar diretamente pelo número da linha, os problemas da vida real nos mostram que nem sempre temos o privilégio de saber exatamente em qual linha estão os dados procurados.

E quanto mais se trabalha com dados, mais se aprende que não é bom confiar em posições fixas porque elas raramente são fixas de verdade. Elas se mudam sem deixar telefone, email... &:)

Tendo em vista estas necessidades, também podemos definir endereços que sejam palavras ou trechos de uma linha.

Por exemplo, hoje no lanche comeremos frutas que... PÁRA! Não é odioso ter em livros técnicos esses exemplos "didáticos" envolvendo bananas, maçãs e abacaxis? Não é repugnante o autor tratar os leitores como crianças de primário? Seus problemas acabaram! Nada de exemplos com frutas por aqui.

Hoje, no lanche, comeremos verduras :) e como somos modernos e informatizados (nerds), colocamos a lista de compras num arquivo.

  • prompt$ cat verduras.txt
  • - alface
  • - cenoura
  • - couve
  • - nabo
  • - vagem


Mas, como hoje não é um dia bom para comer couve, vamos apagá-­la da lista:

  • prompt$ sed '/couve/ d' verduras.txt
  • - alface
  • -­ cenoura
  • - nabo
  • - vagem

Note que, para especificar uma palavra como endereço, devemos colocá-­la entre /barras/. Dentro dessas barras, pode­se colocar qualquer padrão que se queira casar para se encontrar uma linha. Pode ser uma ou mais palavras, símbolos ou expressões regulares.

A melhor maneira de se ler um endereço composto por um padrão é: "Nas linhas que contenham a palavra XXXX, aplique o comando YYYY".


Note bem: "naS linhaS".


Um endereço pode servir para mais de uma linha, então o comando será aplicado em todas as linhas que forem encontradas. Dessa forma, para apagar da nossa lista de verduras todas as linhas que tenham a letra "o", fazemos:

  • prompt$ sed '/o/ d' verduras.txt
  • - alface
  • - vagem

Apenas como curiosidade, este exemplo gera um resultado idêntico ao do comando:

  • prompt$ grep ­v o verduras.txt


[editar] Como aplicar vários comandos em um mesmo endereço

Agora que já sabemos quais são os dois tipos básicos de endereço e como defini­los, vamos começar a complicar um pouco o assunto.

Digamos que você goste muito de couve, mas muito mesmo, e queira que ela apareça mais três vezes na lista de verduras. Teremos então três comandos a serem aplicados na mesma linha:

  • prompt$ sed '/couve/ p ; /couve/ p ; /couve/ p' verduras.txt
  • - alface
  • - cenoura
  • - couve
  • - couve
  • - couve
  • - couve
  • - nabo

­*- vagem

Ficar repetindo o endereço para cada comando é muito inconveniente. Utilizamos então as chaves {}, que nos permitem agrupar vários comandos em um mesmo bloco. Este bloco recebe um endereço, que será usado por todos os comandos contidos nele.

Nosso exemplo anterior fica assim:


  • prompt$ sed '/couve/{ p; p; p; }' verduras.txt


Preste atenção no seguinte detalhe: um bloco está sempre associado a um endereço, e a chave de fechamento "}" é como se fosse um comando do sed, devendo portanto ser separada de outros comandos com ponto­e­vírgula ";".


Ao fechar o bloco, coloque sempre um ; antes da }


Sei que você leu e entendeu, mas tenha absoluta certeza de que você vai se esquecer desse último ";" várias, diversas, milhares de vezes. &:)

[editar] Como endereçar um trecho entre duas linhas incluindo-­as

Tudo ia bem na nossa vida de endereçadores, até o dia em que precisamos endereçar mais de uma linha. Ou melhor, um trecho de texto entre duas linhas.

Por exemplo: como apagar da linha 5 à linha 10 de um arquivo? Numa primeira tentativa, afobada, a carreirinha


  • prompt$ sed '5d ; 6d ; 7d ; 8d ; 9d ; 10d' arquivo


... funciona. Mas e se quiséssemos apagar 20 ou 30 linhas? Esse método não é prático.

No sed, podemos especificar dois endereços, um de início e outro de fim, para representar os limites de um trecho entre duas linhas. Uma vírgula separa os dois endereços. Então, a tarefa anterior de apagar as linhas fica assim:


  • prompt$ sed '5,10 d' arquivo


Ou seja, da linha 5 até a linha 10, apague tudo. Lembre-­se porém, que um endereço também pode ser o cifrão $ ou um padrão. Então agora podemos misturar todos os tipos de endereço.

Veja os exemplos:


  • 1,/couve/
  • 10,$
  • /couve/,$
  • /couve/,/vagem/
  • 1,$
  • da primeira linha até a linha que contém 'couve'
  • da linha 10 até o fim do arquivo
  • da linha que contém 'couve' até o fim do arquivo
  • da linha que contém 'couve' até a linha que contém 'vagem'
  • da primeira linha até a última (ou seja, todas)


Como pudemos notar, o trecho casado inclui as linhas de início e fim do endereço. Se não era este seu objetivo, veja o tópico seguinte.


[editar] Como endereçar um trecho entre duas linhas excluindo-­as

No tópico anterior, vimos como endereçar um trecho contendo várias linhas e aprendemos que limitam o trecho também são incluídas no endereço.

Mas, às vezes, queremos endereçar apenas as linhas que estão dentro de um trecho, excluindo as linhas delimitadoras de início e fim. Nesse caso, precisamos de algo mais elaborado.

Vamos usar um outro arquivo de exemplo, com os números de um a seis por extenso:

  • prompt$ cat numeros.txt
  • um
  • dois
  • três
  • quatro
  • cinco
  • seis

Supondo que se queira destacar tudo entre as linhas que contiverem um e cinco, incluindo as próprias linhas delimitadoras, temos:


  • prompt$ sed '/um/,/cinco/ s/^/­­­/' numeros.txt
  • ---­um
  • ---­dois
  • ---­­­três
  • ---­­­quatro
  • ---­­­cinco
  • seis


Mas, se quisermos excluir as linhas delimitadoras, devemos tratá-­las individualmente, dentro de um bloco:


  • prompt$ sed '/um/,/cinco/ { /um/ b ; /cinco/ b ; s/^/­­­/ ; }' numeros.txt
  • um
  • ---­­­dois
  • ---­­­três
  • ---­­­quatro
  • cinco
  • seis


Mais detalhadamente:

  • /um/,/cinco/ {
  • /um/ b
  •  :#
  • /cinco/ b
  •  :#
  • s/^/---/
  •  ;# entre a linha 'um' e a linha 'cinco' ...
  •  ;# se for a linha 'um', salte para o fim do script e
  • processe a próxima linha (ou seja, não faça nada nesta)
  •  ;# se for a linha 'cinco', salte para o fim do script e
  •  ;# processe a próxima linha (ou seja, não faça nada nesta)
  • nos outros casos, coloque '­­­' no começo da
  • }

Então, o comando b sozinho funciona como um pulo, uma exceção que não deixa o processamento chegar até o s/^/---­­­/ nas linhas especificadas.


[editar] Como negar um endereço, não aplicando comandos nele

Assim como podemos definir um endereço para aplicação de um comando, também podemos determinar que um comando não seja aplicado nesse endereço, ou seja, que o comando se aplica a todas as linhas menos aquela. Veja como apagar todas as linhas do arquivo menos a linha 5:


  • prompt$ sed '5! d' arquivo


O modificador de endereço ! é o responsável pela inversão, e essa lógica às avessas pode confundir. Uma leitura desse comando poderia ser: "Na linha 5, não a apague".

Para facilitar o entendimento correto de sua função, leia o comando de maneira inversa, usando a palavra "exceto" ou "menos". Assim: "Apague TODAS as linhas EXCETO a linha 5".

O mesmo vale para um bloco de comandos e para um endereço duplo:


  • /padrão/! { p; d; }
  • /padrão1/,/padrão2/! { p; d; }


Com blocos, a leitura fica: "Aplique os comandos do bloco em TODAS as linhas EXCETO as do endereço".

Assim sendo, fica fácil mostrar apenas um trecho de texto e apagar todo o resto:


  • prompt$ sed '/dois/,/cinco/! d' numeros.txt
  • dois
  • três
  • quatro
  • cinco


Em outro exemplo, vamos emular o comando head do UNIX, que imprime as 10 primeiras linhas de um arquivo. Uma das possibilidades seria esta:


  • sed '11,$ d'
  • apague da linha 11 até o fim do arquivo


Ou então, usando o modificador !, podemos inverter a lógica e fazer assim:


  • sed '1,10! d'
  • apague todas as linhas EXCETO da linha 1 até a 10

Pode parecer estranho (e é!) pensar invertido, mas você se acostuma &:)


[editar] Como endereçar a primeira ocorrência de um padrão

Como já vimos, o endereço pode servir para mais de uma linha. Após encontrar a primeira ocorrência do padrão e executar o comando, o sed continua lendo o arquivo. O comando então será novamente aplicado em todas as outras linhas em que o padrão for encontrado.

Mas, e para endereçar apenas a primeira ocorrência do padrão e não as restantes?

Aqui, precisamos de um pouco de criatividade. A idéia é especificar uma "área de atuação" para o comando, para que seu endereço somente seja válido nesta área.

Para nosso objetivo, a área deve começar no início do arquivo e ir até a primeira linha onde o padrão for encontrado. Ei, já sabemos como fazer isso, basta endereçar!


  • 1, /padrão/ { comandos ; }


Usando a linha 1 como primeiro endereço, conseguimos "ancorar" o bloco de comandos no início do arquivo. Dessa maneira garantimos que apenas a primeira ocorrência do padrão está contida neste endereço.

Definida nossa área de atuação, agora ficou fácil! Por exemplo, para modificar somente a primeira linha que contenha a letra "o" no nosso arquivo de números:


  • prompt$ sed '1,/o/{ /o/ s/^/---­/ ; }' numeros.txt
  • um
  • ­­­---dois
  • três
  • quatro
  • cinco
  • seis


Vamos ler este comando: "Dentro da área que vai da primeira linha até a linha que conter a letra 'o', faça: se for uma linha com 'o', adicione '­­­' no começo".

Há também uma tática mais simples, que funciona em casos específicos. Para tarefas como extração de dados, onde se busca apenas a primeira ocorrência e nada mais, o comando de interrupção de processamento pode ser utilizado.

A idéia é simples: colocar num bloco o comando desejado e a interrupção. Como a interrupção só pode ser executada uma vez, está feito nosso "condicional" para obter apenas o primeiro.

Exemplo: extrair do arquivo apenas a primeira linha com a letra "o":


  • prompt$ sed ­-n '/o/{ p; q; }' numeros.txt
  • dois


Com a opção ­-n, silenciamos a saída do sed. Na linha com "o", é executado um Print para mostrá-­la e um Quit para sair.

A mesma tática é utilizada para extrair os cabeçalhos de um e­mail, que ficam sempre no início, separados do corpo da mensagem por uma linha em branco:


  • sed '/^$/q' email.txt


Traduzindo: "Ao encontrar a primeira linha em branco do arquivo, saia".


[editar] Como endereçar a primeira ocorrência de um trecho

Um endereço duplo também está sujeito às mesmas regras de um endereço normal, então um trecho de texto pode se repetir várias vezes e o seu comando (ou bloco) será executado tantas vezes quantas necessárias.

Assim sendo, o comando /(/,/)/ d apagará TODOS os trechos do texto que estiverem delimitados por parênteses, podendo o parêntese que fecha estar numa linha diferente do que abre.

Para limitar esse endereço ao primeiro trecho encontrado, usamos a mesma tática de ancoragem com a primeira linha usada no tópico anterior:


  • 1,/)/ {
  • /(/,/)/ d
  • }

Traduzindo: "Do começo do texto até a primeira linha que contiver ')', apague tudo que estiver entre os parênteses, inclusive eles próprios".


[editar] Como endereçar a última ocorrência de um padrão

Entramos agora num tópico complexo. Endereçar a primeira ocorrência de um padrão ou trecho é algo relativamente fácil. Mas a última não é tão simples:


  • prompt$ sed '/o/,${ /o/ d ; }' numeros.txt
  • um
  • três
  • seis


A tentativa óbvia falha porque o sed lê as linhas de cima para baixo, então o endereço especificado acaba significando: "Procure da primeira linha que tiver a letra 'o' até a última linha do texto".

Para fazermos um s/primeira/última/ com este significado, temos algumas alternativas. A mais fácil, porém exclusiva de quem possui o comando tac, que imprime um arquivo da última para a primeira linha, pode ser:


  • prompt$ tac numeros.txt
  • seis
  • cinco
  • quatro
  • três
  • dois
  • um


  • prompt$ tac numeros.txt | sed '1,/o/{ /o/ d ; }' | tac
  • um
  • dois
  • três
  • quatro
  • seis


Então, invertendo a ordem das linhas, aplicamos o mesmo conceito de ancorar pela primeira linha do arquivo (que na verdade é a última), e então basta desinverter o arquivo aplicando tac novamente.

Quem não possui o tac, pode emulá-­lo com o sed:


  • prompt$ sed '1! G ; h ; $! d' numeros.txt
  • seis
  • cinco
  • quatro
  • três
  • dois
  • um


E trocando o tac do comando anterior por este em sed, ficamos com:


  • sed '1!G;h;$!d' numeros.txt | sed '1,/o/{ /o/ d ; }' | sed '1!G;h;$!d'


Feio, muito feio. E esse método de inversão complica tudo, pois o script a ser aplicado ao trecho, caso seja necessário mexer com mais de uma linha, também precisa tratá­las de maneira invertida. Em algumas situações onde se tem uma seqüência certa de linhas, inverter a lógica pode ser impraticável.

Mas, com exceção dessa tática, não há muitas alternativas. Dependendo do que precisamos fazer com o padrão, podemos usar outras abordagens.

Por exemplo, se precisarmos simplesmente imprimir na tela a linha da última ocorrência,

podemos usar o sed duas vezes:


  • prompt$ sed '/o/! d' numeros.txt | sed '$! d'
  • cinco

O primeiro obtém todas as linhas que contêm o padrão, e o segundo mostra apenas a última linha, apagando todas as outras. Um comando UNIX similar seria:


  • prompt$ grep 'o' numeros.txt | tail ­1


Para fazer isso usando apenas um sed, podemos utilizar o RESERVA para ir armazenando (sobrescrevendo) todas as linhas que contém o padrão, e quando chegarmos na última linha do arquivo, mostramos o conteúdo do RESERVA:


  • prompt$ sed '/o/ h ; $! d ; $ g' numeros.txt
  • cinco


Para entender melhor:


  • /o/ h
  • $! d
  • $ g
  •  ;# guarde (sobrescrevendo) no RESERVA as linhas que contêm 'o'
  •  ;# apague todas as linhas, menos a última
  •  ;# se for a última linha, pegue o conteúdo do RESERVA

Infelizmente, não há uma "receita de bolo" para endereçar de maneira genérica a última ocorrência. Cada caso é um caso, e merece uma solução personalizada.


[editar] Como endereçar a N­ésima ocorrência de um padrão

Fazendo magia negra. :)

Já vimos como é complicado endereçar a última ocorrência do padrão, e não há como fazer isso de maneira genérica. Agora imagine especificar uma posição arbitrária de repetição de um padrão.

Caso seja algo simples, como só mostrar na tela a terceira linha que contenha a letra "o", por exemplo, podemos usar a tática anterior de usar o sed duas vezes:


  • prompt$ sed '/o/! d' numeros.txt | sed '3! d'
  • cinco


Sim, é possível, dependendo do caso. Mas contando que o grau de dificuldade é enorme para se fazer isso num problema mais elaborado, além do código resultante ficar grande e difícil de se manter, o aconselhável é usar outra linguagem, como awk, que tem o conceito de linhas e registros, incluindo contadores, ou usar o sed em conjunto com um script shell.

Como nem um (shell) nem outro (awk) faz parte do nosso tópico, fim de papo.


[editar] Tem uma barra / no padrão que procuro, e agora?

Ao procurar linhas com datas, nomes de diretórios, ou outros padrões que possuem o caractere "/" podemos nos surpreender com as mais diversas mensagens de erro:


  • prompt$ sed '//tmp/lixo.bmp/ d' arquivo
  • sed: ­e expressão #1, caractere 2: não há uma expressão regular anterior


  • prompt$ sed '/tmp/lixo.bmp/ d' arquivo
  • sed: ­e expressão #1, caractere 7: há caracteres sobrando após o comando


  • prompt$ sed '/31/12/2000/ d' arquivo
  • sed: ­e expressão #1, caractere 5: comando desconhecido: ‘1'


As mensagens de erro do sed são em geral curtas e esclarecedoras, mas no caso de problema com o delimitador, elas ficam especialmente obscuras. Anote em algum canto de seu cérebro:


Se a mensagem de erro do sed é alienígena, confira os delimitadores.


O que aconteceu em todos os exemplos é que o sed confundiu a barra normal dos padrões com a barra delimitadora do endereço. O que temos que fazer aqui para que essas barras normais não sejam interpretadas como delimitadoras, é escapá­las.

Segundo o Aurélio (o outro), um dos significados de escapar é "passar despercebido". E é exatamente isso o que precisamos: fazer com que a barra não seja considerada especial pelo sed. Quem faz esta mágica é a barra invertida "\", que colocada antes de um caractere normal, o escapa, impedindo que o sed o considere um delimitador de endereço.

Em nosso caso, a barra "/" escapada fica: \/. Como dica geral, sempre escape as barras normais do padrão, evitando dores de cabeça.

Mas vamos voltar aos exemplos anteriores, agora com as barras devidamente escapadas:


  • prompt$ sed '/\/tmp\/lixo.bmp/ d' arquivo
  • prompt$ sed '/tmp\/lixo.bmp/ d' arquivo
  • prompt$ sed '/31\/12\/2000/ d' arquivo


Note que são escapadas apenas as barras internas, as barras delimitadoras do endereço continuam as mesmas.


[editar] Como usar outro delimitador fora a barra /

Agora cá entre nós, esse negócio de ficar escapando barras não é nem um pouco prático. Pior ainda se nosso padrão estiver dentro de uma variável, e não for tão visível o conflito de barras, como em sed "/$PWD/ d", onde $PWD contém o diretório atual de trabalho.

Para esses e outros possíveis problemas, o sed nos dá a liberdade de escolher qualquer caractere da tabela ASCII como delimitador de endereço.

Então como regra geral para escolher qual símbolo usar, utiliza­se um delimitador que se tem certeza que não vai ser confundido com nenhum caractere do padrão a ser procurado.

O único porém para usar algo diferente da barra /, é que precisamos dizer ao sed: "Ei, o próximo caractere aqui vai ser o delimitador". Para isso, basta escapar o primeiro (somente o primeiro!) delimitador, assim:


  • sed '\,31/12/2000, d' arquivo


Então utilizamos a vírgula como delimitador de endereço, escapando a primeira para que o sed a veja como especial.


Delimitador: escolha qualquer ASCII, escape o primeiro


Fora a vírgula, outros delimitadores reserva que são clássicos de usar é a barra vertical |, o arroba @ e a exclamação !. Agora, para evitar dor de cabeça e usar um delimitador que dificilmente coincidirá com qualquer padrão, use caracteres esquisitos como: §, £ e ¢.

Ou ainda, se você quiser fazer algo realmente esquisito, use caracteres brancos como o espaço ou o TAB, ou letras normais do alfabeto, números (cuidado!)


  • sed '\ 31/12/2000 d' arquivo


  • sed '\i31/12/2000i d' arquivo


  • sed '\831/12/20008 d' arquivo


Ou pior ainda, até a quebra de linha pode ser usada!


  • prompt$ sed '\
  • > 31/12/2000
  • > d' arquivo


Quem disse que o sed não é divertido? &:)

Uma regra boa para a escolha do delimitador, além dele não coincidir com algum caractere do padrão, é que ele seja visualmente oposto ao padrão. Isso quer dizer que se o seu padrão tiver uma predominância de caracteres altos, como uma palavra em MAIÚSCULAS, é aconselhável usar caracteres baixos como delimitadores, como a vírgula, dois pontos e o sublinhado. E se o padrão for predominantemente baixo, caracteres como / ! | % e @ são mais indicados. Veja:

  • visual
  • s_$PWD_/TMP_
  • s|...=|=|
  • confuso
  • s!$PWD!/TMP!
  • s:...=:=:


[editar] O que acontece com endereços inválidos

Um endereço inválido pode ser uma palavra que não é encontrada em nenhum lugar no texto, ou uma linha que não existe, por exemplo a linha 20 num arquivo que só possui 15 linhas.

E agora a dúvida que assola a humanidade: "E se eu colocar um endereço inválido?" a resposta é: depende.

Se for um endereço simples de uma linha, o comando simplesmente não vai ser executado, pois nenhuma linha satisfez o padrão.

Se for um endereço de um trecho entre duas linhas, o resultado é diferente caso o endereço inválido seja o primeiro ou o segundo. Vamos acompanhar um exemplo utilizando a palavra prego como padrão no nosso arquivo de verduras:


  • prompt$ sed '/prego/,$ d' verduras.txt
  • -alface
  • -cenoura
  • -couve
  • -nabo
  • -vagem
  • prompt$ sed '1,/prego/ d' verduras.txt
  • prompt$


No primeiro exemplo, era para apagar da linha que tivesse prego até o final. Como nenhuma linha tinha prego, o primeiro endereço falhou e nada foi apagado.

Já no segundo exemplo era para apagar da primeira linha até a linha que tivesse prego. A primeira linha foi encontrada, então o comando d vai ser aplicado até achar o segundo endereço. Mas como não tem prego no arquivo, o comando vai sendo aplicado até bater na última linha. É como dizer: "Ninguém me mandou parar, então continuei".

  • 1º endereço inválido: endereço inválido, nada acontece
  • 2º endereço inválido: endereço incompleto, vai até o final


[editar] E se eu colocar um endereço vazio como //?

Um endereço vazio referencia o último endereço pesquisado.

Essa é uma funcionalidade muito útil e que pode poupar muita redundância no endereço.

Lembra do exemplo de imprimir mais três vezes a linha da couve?


  • prompt$ sed '/couve/ p ; /couve/ p ; /couve/ p' verduras.txt


Esse comando poderia ser reescrito assim:


  • prompt$ sed '/couve/ p ; // p ; // p ' verduras.txt


E aquele outro de ancoragem para imprimir apenas a primeira linha que contivesse a letra o?


  • prompt$ sed '1,/o/{ /o/ d ; }' numeros.txt


Este pode ficar assim:


  • prompt$ sed '1,/o/{ // d ; }' numeros.txt


A diferença parece pequena porque os exemplos usam endereços bem simples, mas quando eles são monstros gigantes que cospem expressões regulares, o endereço vazio ajuda muito.

E tem ainda o clássico //s/// que é um comando válido e bem utilizado. Você se arrisca a adivinhar o que ele faz?


[editar] E se eu não colocar nenhum endereço?

O comando serve para todas as linhas. Ponto.


  • prompt$ sed 'd' verduras.txt
  • prompt$


[editar] Detalhes sórdidos sobre endereços

Apesar de os tópicos anteriores esmiuçarem vários aspectos do endereço, ainda restam alguns detalhes que devem ser assimilados.


  • Os únicos comandos que não recebem endereço são o : e o }, ambos por serem comando relativos a posições do próprio script e não do texto que está sendo

processado.

  • Os comandos que não recebem dois endereços são =, a, i, q e r, pois só podem ser aplicados a apenas uma linha por vez. Mas se diretamente não conseguimos endereçá­

los a um trecho como em 1,5=, usando blocos não há problema: 1,5{ =; }

  • Os comandos que devem receber endereço são ! e {. O ! por ser um inversor de endereços, aplicando o comando exceto no endereço indicado e o { que define um bloco de comandos a ser aplicado no endereço especificado. Se não tiver endereço, o bloco será aplicado para todas as linhas tornando desnecessário o agrupamento.
  • As linhas de início e fim de um trecho não podem ser a mesma linha, então mesmo que seu endereço tenha padrões idênticos como /cebola/, /cebola/, serão necessárias duas linhas diferentes que contenham a palavra cebola para que este endereço seja

encontrado. Então um trecho sempre tem no mínimo, duas linhas.

  • O \n como identificador de quebra de linha no endereço só é válido após a aplicação do comando N, que gruda linhas no PADRÃO separando­as pelo \n. Do contrário, não haverá mais de uma linha no PADRÃO para se processar.

[editar] Arquivo

Já vimos que o sed não é um editor de arquivos, mas de fluxos de texto. Como tal, a sua função é aplicar os comandos de edição no texto e mostrar o resultado na tela.

Mas mesmo tendo seu funcionamento independente do conceito de arquivos, o sed precisa lidar com eles, pois nós humanos gostamos de arquivos!

Temos nossos dados guardados em arquivos, então o sed precisa ter a capacidade de ler e gravar linhas em arquivos. Podemos ainda querer guardar todo o resultado da edição feita pelo sed em um arquivo. Ou ainda, podemos querer colocar nossos comandos sed num arquivo, pois eles estão ficando muito complicados. Feito isso, podemos até torná­lo um arquivo executável, que chama o sed automaticamente para interpretá­lo!

Ufa! Para um programa que edita somente fluxos de texto, até que temos vários detalhes para ver no assunto "Arquivo", não? &:)


[editar] Como gravar o resultado num arquivo

Apesar do sed ter sido concebido como um filtro, que repassa o fluxo de texto para a tela do computador, nós usuários também temos necessidades de armazenamento desse fluxo.

Seja para consulta posterior, para edição de arquivos (e não fluxos) ou para registros e extração de dados, é importante guardamos o texto processado pelo sed em um arquivo.

Mas sendo o sed um filtro de fluxos, o que ele entende de arquivos? Quase nada. Por isso a solução desse problema está um pouco mais embaixo, no shell, que é o ambiente onde o sed é executado.

A tela é chamada de "saída padrão" do sed, o caminho natural que o fluxo segue após ser processado. Mas como essa saída passa também pelo shell antes de ir para a tela, ele tem meios de "desviá­la" e mandá­la para um arquivo. Esse desvio é chamado de "redirecionamento", e o caractere utilizado para representá­lo é o maior­que ">".


Informatiquês: Precisamos redirecionar a saída padrão!


Então aplicamos os comandos sed desejados no texto e redirecionamos o resultado dessa edição para um arquivo:


  • prompt$ sed 'comandos' texto.txt > texto­alterado.txt


Este redirecionamento é chamado destrutivo, pois caso já exista o arquivo texto­alterado.txt, ele será "truncado". Truncar em informatiquês significa cortar, apagar, excluir, remover, limpar, zerar, ou seja, o conteúdo anterior do arquivo será perdido. Caso o arquivo ainda não exista, o shell o criará.

A outra opção é usar o redirecionamento incremental, representado por dois sinais de maior­ que ">>". Ao contrário do destrutivo, se o arquivo já existir, seu conteúdo original será preservado e o texto novo será anexado após a última linha. Caso o arquivo ainda não exista, o shell o criará.


  • prompt$ sed 'comandos' texto.txt >> texto­alterado.txt
  • prompt$ sed 'outros­comandos' texto.txt >> texto­alterado.txt


Memorizar é fácil! �

  • Usando...
  • >
  • >>
  • ...acontece
  • o arquivo é sempre zerado
  • o arquivo vai crescendo, crescendo...

A maioria dos ambientes shell conhecidos utiliza a notação do > e >> para redirecionamentos, aqui vão alguns deles:


  • no UNIX/Linux: sh, ash, bash, ksh
  • no Macintosh: MPW Shell
  • no Windows: MS­DOS


[editar] Como gravar o resultado no próprio arquivo original

Olhe ali para aquela câmera, você acaba de cair na pegadinha de gravar no mesmo arquivo!


  • Quem nunca apagou um arquivo importante para descobrir que o UNIX/Linux não tem

undelete nem "Lixeira"?

  • Quem nunca perdeu um HD inteiro para descobrir a importância do becape?
  • Quem nunca apertou o botão "Ok" na mensagem "Deseja sair sem salvar as alterações?" e teve que redigitar o documento?
  • Quem nunca deu um rm ­rf / tmp/lixo ou um rm ­rf * .txt ou um rm ­rf .* ?


Pois é amigo, brincadeiras à parte, fazer cacas irreversíveis em informática é tão fácil que uma simples apertada na tecla Enter na hora errada pode destruir um dia de trabalho (ou vários...).

Essa introdução descontraída serve para aliviar o peso da culpa daqueles que já descobriram pelo jeito difícil que gravar o resultado no mesmo arquivo pode ser cruel &:)


[editar] Problema inicial

Como já visto, utiliza­se o redirecionamento do shell para gravar o resultado do sed num outro arquivo. Mas é muito comum ao se editar um determinado arquivo, querer gravar estas alterações no próprio arquivo original. Faz sentido não faz?

Então a tentativa óbvia e intuitiva é o clássico tiro no pé:


  • prompt$ sed 'comandos' texto.txt > texto.txt


Além de não dar certo, todo o conteúdo original do arquivo será perdido. Que dureza hein?

Mas se é errando que se aprende, caso você nunca tenha feito essa caquinha (ainda), agora é sua chance de aprender sem precisar sofrer.

O que ocorre é que ao fazer o redirecionamento destrutivo >, a primeira coisa que o shell faz é truncar o arquivo referenciado, antes mesmo de começar a executar o sed. Então o conteúdo do arquivo será apagado antes do sed poder lê­lo.

Quando finalmente o sed for chamado pelo shell, receberá um arquivo vazio, e aí não dá para fazer mágica: aplicados quaisquer comandos num arquivo vazio, o resultado será sempre um arquivo vazio. E fim de papo.


[editar] Solução genérica

Para solucionar este dilema, voltamos a tática já conhecida de redirecionar o resultado para um outro arquivo, e depois mover o arquivo novo sobre o original:


  • prompt$ sed 'comandos' texto.txt > texto­alterado.txt
  • prompt$ mv texto­alterado.txt texto.txt


Para a grande maioria dos casos, isso é suficiente. Porém caso as características do arquivo original devam ser mantidas, temos um problema.

Como acabamos de criar um arquivo novo para guardar o texto alterado, ele terá os atributos padrão do sistema. Ao movê­lo sobre o arquivo original, estes atributos padrão serão herdados.

Atributos especiais que o arquivo original pudesse ter como: grupo diferente do padrão do usuário, permissões específicas (rwx) ou referências para outros arquivos (links, simbólicos ou não) serão perdidos.


[editar] Solução segura

Para uma solução à prova de falhas, usa­se uma abordagem mais conservadora e segura: copiar o arquivo e redirecionar a saída para o original.

A primeira ação a se fazer é copiar o arquivo original para um arquivo temporário qualquer. Feito isso, então se aplica os comandos sed neste temporário. Agora vem o pulo do gato: redireciona-­se a saída do sed para o arquivo original:


  • prompt$ cp texto.txt texto­tmp.txt
  • prompt$ sed 'comandos' texto­tmp.txt > texto.txt


Dessa maneira não estamos criando um arquivo texto.txt novo, mas apenas trocando o seu conteúdo, permanecendo inalteradas as suas características no sistema. Para finalizar a operação, basta apagar o arquivo temporário.


  • prompt$ rm texto­tmp.txt


É importante ter em mente a importância desta abordagem segura e o porquê de utilizá-­la. Não apenas para o sed, mas também para outros comandos do sistema que atuam como filtros, mandando o resultado para a tela.


[editar] Solução moderna

Para evitar todas essas preocupações e não ter que criar um arquivo temporário, algumas versões do sed (FreeBSD, ssed, gsed­4) possuem a opção "­i", que aplica os comandos "in­ place", ou seja, no mesmo arquivo.

Dessa forma, não se depende mais do shell para contornar a situação:


  • prompt$ sed ­i 'comandos' texto.txt


[editar] Como guardar os comandos sed num arquivo (script)

À medida que vamos evoluindo no aprendizado, os comandos sed vão ficando cada vez mais extensos e complicados. Ficar sempre redigitando tudo na linha de comando torna­se um incômodo. Seria interessante poder guardar os comandos num arquivo!

Nesse momento em que sentimos a necessidade de algo mais prático, presenciamos nossa própria evolução, a passagem da infância para a adolescência no aprendizado do sed. Ao invés de fazer um "comando sed", agora vamos fazer um "script sed", com bastante comandos e estruturado. Na fase adulta de aprendizado, podemos ainda evoluir para um "programa sed", que ao invés de editar textos, os domina.

O simples fato de colocar os comandos sed num arquivo ao invés de digitá-­los na linha de comando, muda alguns aspectos da brincadeira divertida de manipular texto. Vamos aos detalhes sórdidos!

A primeira vantagem é que não precisamos mais nos preocupar com o shell, tendo que proteger os comandos entre aspas para não serem confundidos e interpretados. Dentro do arquivo podemos colocar quaisquer caracteres, despreocupadamente.

Mas é no mesmo ponto que temos a primeira desvantagem: perdemos o contato com o shell, nosso aliado poderoso. Com suas variáveis, operações e comandos, o shell complementa onde o sed é limitado. Quando colocamos os comandos sed num arquivo, eles tornam­se estáticos, e a interatividade direta com o shell é perdida.

Uma outra vantagem da utilização do arquivo, é que podemos separar melhor os comandos, colocando um em cada linha. Podemos fazer inclusive o alinhamento estruturado ("indentation") para que os blocos de comandos fiquem mais visuais.

E mais, agora podemos colocar também comentários! Desprezado por muitos, eles são uma das partes mais importantes de um programa. Só quem já precisou dar manutenção num código complexo e sem comentários (ou mal comentado) sabe a falta que eles fazem...

E por favor, pelo seu próprio bem e dos que um dia precisarem dar manutenção num script sed de sua autoria, encha-­o de comentários! O sed é especialmente abstrato e desafiador, com seus comandos de apenas uma letra, sem variáveis e muitas expressões regulares. A falta de comentários pode deixar um script inutilizável se ninguém souber como atualizá­lo. O caractere que precede os comentários num script sed é aquele que cada um chama de um jeito: #. Gradinha, cerquilha, sustenido, jogo da velha, hash, ...


Script sed: shell­­, alinhamento++, comentários++, legibilidade++


Vamos relembrar o exemplo de endereçar entre duas linhas, excluindo­as:


  • prompt$ sed '/um/,/cinco/ { /um/ b ; /cinco/ b ; s/^/­­­ / ; }' numeros.txt


É um endereço com um bloco de comandos dentro das chaves {}. No bloco estão três comandos, separados por ponto­e­vírgula. Há uma certa dificuldade em identificar e entender as partes desse comando, por estar tudo misturado numa única linha. Vamos colocá­lo num arquivo e ver se melhora?

 ### meu primeiro script sed
 # ei, isso é um comentário!

 # vamos delimitar um bloco de linhas
 # endereço: entre a linha que contém 'um' e a linha que contém 'cinco'
 /um/ , /cinco/ {

        ### nesse ponto do script, só chegarão as linhas que estiverem entre
        ### 'um' e 'cinco', incluindo elas próprias.
        
        # se for a linha que contém 'um', vá até o final do script
        /um/ b

        # se for a linha que contém 'cinco', vá até o final do script
        /cinco/ b

        ### nesse ponto do script, as linhas 'um' e 'cinco' já não chegam mais.
        ### então somente para as outras será aplicado o próximo comando.

        # coloque um '---­­­' no começo da linha
        s/^/---­­­/

 # fim do bloco
 }

 ### fim do script
 

Tudo bem, os comentários estão exageradamente explicativos, mas a idéia é mostrar o quão didático pode ficar seu script sed. Com certeza ficou 78% mais fácil de ler! (Não são irritantes essas estatísticas subjetivas e absurdas que ouvimos diariamente?)

É notável a diferença do entendimento entre este script e a linha de comando anterior onde os comandos estavam todos grudados. Moral da história: fica a critério do programador fazer um código pequeno e indecifrável, ou extenso e legível. Ambas as formas têm suas audiências.

Como os espaços em branco no começo de cada linha são irrelevantes para o sed, temos a liberdade de estruturar as linhas como preferirmos, podendo utilizar espaços e TABs. Note também que como separamos os comandos um por linha, não precisamos mais colocar o ponto­e­vírgula entre eles.

Cada um tem a liberdade de nomear os scripts sed como bem entender, não há um padrão ou norma. Mas para facilitar a identificação de seu conteúdo, é aconselhável utilizar a extensão .sed. Este script de exemplo poderia se chamar seu­madruga­no­brasil.avi, mas bloco.sed é muito mais descritivo e apropriado, não concorda? &:)


[editar] Agradecimentos

Em Outubro de 2002, alguns amigos toparam ajudar com o livro. Eu lhes mostrei o texto e eles sugeriram melhorias, arrumaram erros ortográficos e fizemos uma grande discussão que resultou no aumento de qualidade do conteúdo. A esse grande time meu MUITO OBRIGADO:


  • Eliphas Levy Theodoro
  • Érico "DyNaMiTe"
  • Fernando Braga
  • Gentil de Bortoli Júnior
  • Julio Cezar Neves
  • Leslie Harlley Watter
  • Luciano Espírito Santo
  • Marcelo Pereira
  • Rodrigo Stulzer
  • Rubens Queiroz de Almeida
  • Thobias Salazar Trevisan