Dominando o SED

Wikisource, a biblioteca livre
Saltar para a navegação Saltar para a pesquisa

Nota do autor[editar]

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!

Instalação[editar]

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!


UNIX/Linux/Mac OS X[editar]

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.


Windows/DOS[editar]

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!


Endereço[editar]

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


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

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"


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

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.


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

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.


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

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

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

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. &:)

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

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.


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

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 linha '^'
  • }

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.

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

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 &:)


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

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".

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

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".


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

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.


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

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.


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

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.


Como usar outro delimitador fora a barra /[editar]

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:...=:=:


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

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


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

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?


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

O comando serve para todas as linhas. Ponto.


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


Detalhes sórdidos sobre endereços[editar]

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.

Arquivo[editar]

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? &:)


Como gravar o resultado num arquivo[editar]

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


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

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 &:)


Problema inicial[editar]

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.


Solução genérica[editar]

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.


Solução segura[editar]

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.


Solução moderna[editar]

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


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

À 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? &:)


Agradecimentos[editar]

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