Sed
Autor: Lucas Sabino | Publicado: 2012-02-03T22:36:00-02:00



O que é?

O Sed é um editor de textos não
interativo.

Ele pode editar automaticamente, sem
interação do usuário, vários arquivos seguindo um conjunto de
regras especificadas. 







O que significa a palavra Sed

Vem do inglês "Stream EDitor",
ou seja, editor de fluxos (de texto). 







Como saber se devo usar o Sed

Sendo um editor de textos não
interativo, o Sed é excelente para desempenhar algumas tarefas, mas
em outras seu uso não é aconselhado. 







Quando usar o Sed

A característica principal do Sed é
poder editar arquivos automaticamente. 





Então sempre que você precisar fazer
alterações sistemáticas em vários arquivos, o Sed é uma solução
eficaz. 





Por exemplo, você tem um diretório
cheio de relatórios de vendas, e descobriu que por um erro na
geração, todas as datas saíram erradas, com o ano de 1999 onde era
para ser 2000. Num editor de textos normal, você tem que abrir os
relatórios um por um e alterar o ano em todas as ocorrências. 





Certo, isso não é tão complexo se o
editor de textos possuir uma ferramenta de procura e troca, também
chamado de substituição. 





Mas então suponhamos que o erro da
data não seja o ano, e sim o formato, tendo saído como mm/dd/aaaa
quando deveria ser dd/mm/aaaa. Aqui não é uma substituição e sim
uma troca de lugares, e uma ferramenta simples de procura e troca não
poderá ajudar. 





Esse é um caso típico onde o Sed
mostra seu poder: alterações complexas em vários arquivos. 





Utilizando o Sed, a solução para este
problema (que veremos adiante) é até simples, bastando definir uma
série de regras de procura e troca, e o programa se encarregará de
executá-las e arrumar os relatórios. 







Quando não usar o Sed

Nenhuma ferramenta é ideal para todas
as tarefas, e o Sed não é uma exceção à regra. 





Edição genérica de textos

Ele não é prático para ser utilizado
como editor de textos de uso genérico. 





Para escrever textos, ou alterar coisas
simples, é mais rápido e fácil abrir um editor de textos
interativo como o vi ou o emacs e fazer a alteração "na mão".





Programação avançada

O Sed não é uma linguagem de
programação completa, pois não possui variáveis, funções
matemáticas, interação com o sistema operacional, entre outras
limitações. Mas bem, ele é um manipulador de texto e não uma
linguagem de uso geral. 





Algumas estruturas complexas podem ser
simuladas com alguma técnica, mas se o seu programa em Sed começou
a inchar muito, é aconselhável reescrevê-lo numa linguagem com
mais recursos, como o perl. 







Como ele funciona

O Sed funciona como um filtro, por onde
você passa um texto X e ele joga na saída um texto Y. 





O texto X virou Y seguindo algumas
regrinhas que você determinou. 





Pense no Sed como um processador de
alimentos, dependendo da lâmina utilizada, a batata sai cortada de
uma maneira diferente :) 





o Sed funciona como um filtro, ou
conversor. 





o Sed é orientado a linha, de cima
para baixo, da esquerda para a direita. 





o Sed lê uma linha da entrada padrão
(STDIN) ou de um arquivo especificado, aplica os comandos de edição
e mostra o resultado na saída padrão (STDOUT). vai para a próxima
linha e repete o processo. 





o Sed aceita endereços para os
comandos. 





o Sed aplica os comandos para todas as
linhas caso um endereço não seja especificado. 





o Sed faz uso intensivo de expressões
regulares. 







Sua sintaxe

A sintaxe genérica de um comando Sed
é: 





sed [opções] regras [arquivo]




Sendo que regras tem a forma genérica
de: 





[endereço1 [, endereço2]] comando
[argumento]




Exemplo

Como notação tradicional, o que está
[entre colchetes] é opcional, então a sintaxe Sed mais simples que
existe é sed regra como em: 





prompt$ cat
texto.txt | sed p




Ou seja, o Sed lendo da entrada padrão
o conteúdo do arquivo texto.txt via duto |, aplica o comando p para
todas as linhas do arquivo, ou seja, as duplica. 





0.2.2. Outros exemplos

Um outro exemplo do Sed com opções e
recebendo um arquivo como parâmetro seria: 





prompt$ sed -n p texto.txt




E ainda, agora especificando um
endereço para o comando p: 





prompt$ sed -n 5p texto.txt




Ou seja, este comando imprime apenas a
linha 5 do texto.txt 










Tornando arquivos Sed executáveis

O interpretador de comandos mais
utilizado (bash) sempre procura na primeira linha de um arquivo
instruções para executá-lo. 





Se um arquivo é um programinha em
shell, basta colocar 





#!/bin/sh




Na primeira linha para que o bash saiba
que deve executá-lo com o comando /bin/sh. O mesmo funciona para
qualquer outro interpretador, como o Sed. então para tornar um
arquivos de comandos Sed executável basta colocar como primeira
linha: 





#!/bin/sed -f




E é claro, torná-lo executável: 





prompt$ chmod +x programa.sed




E na linha de comando, chame-o
normalmente: 





prompt$
./programa.sed texto.txt

prompt$ cat
texto.txt | ./programa.sed









Descrição de todos os comandos

prompt$ man sed

prompt$ pinfo sed




Ou num resumo rápido: 





Legenda:




[ARQUIVO]       arquivo ou fluxo de
texto (via pipe) original a ser modificado

[TEXTO]         trecho de texto. pode
ser uma palavra, uma linha,

                várias separadas por
\n, ou mesmo um vazio.

[PADRÃO]        [TEXTO] contido no
ESPAÇO PADRÃO







= imprime o número da linha atual do
[ARQUIVO]

# inicia um comentário

! inverte a lógica do comando

; separador de comandos

, separador de faixas de endereço

{ início de bloco de comandos

} fim de bloco de comandos




s substitui um trecho de texto por
outro

y traduz um caractere por outro




i insere um texto antes da linha atual 


c troca a linha atual por um texto

a anexa um texto após a linha atual




g restaura o [TEXTO] contido no ESPAÇO
RESERVA (sobrescrevendo)

G restaura o [TEXTO] contido no ESPAÇO
RESERVA (anexando)

h guarda o [PADRÃO] no ESPAÇO RESERVA
(sobrescrevendo)

H guarda o [PADRÃO] no ESPAÇO RESERVA
(anexando)

x troca os conteúdos dos ESPAÇO
PADRÃO e RESERVA




p imprime o [PADRÃO]

P imprime a primeira linha do [PADRÃO]

l imprime o [PADRÃO] mostrando
caracteres brancos




r inclui conteúdo de um arquivo antes
da linha atual

w grava o [PADRÃO] num arquivo




: define uma marcação

b pula até uma marcação 


t pula até uma marcação, se o último
s/// funcionou (condicional)




d apaga o [PADRÃO]

D apaga a primeira linha do [PADRÃO]

n vai para a próxima linha

N anexa a próxima linha no [PADRÃO]

q finaliza o Sed imediatamente









Lista de todos os comandos por
categoria

                   informações =

                    marcadores :

                   comentários #

            comandos de edição s i c
a y

     comandos de registradores g G h H
x

         comandos de impressão p P l

           comandos de arquivo r w

                 modificadores g i !

                   separadores ; -e \n

             controle de fluxo b t d D
n N q

                      endereço // ,

                   limitadores {} \(\)

       registradores dinâmicos \1 \2
... \9













Sed e shell

Com o Sed sendo invocado na linha de
comando, deve-se ter alguns cuidados para evitar transtornos. O
interpretador de comandos (shell), interpreta a linha de comando
antes de processá-la, então alguns caracteres especiais como $, \ e
!, são interpretados pelo shell antes de chegarem ao Sed,
modificando o comportamento esperado. 





Para evitar isso coloque os comandos
Sed sempre entre aspas simples: 





prompt$ sed 's/isso/aquilo/' texto.txt




Salvo quando no meio do comando Sed,
existir algo que deva ser interpretado, como uma variável por
exemplo. Neste caso coloque os comandos entre aspas duplas: 





prompt$ sed "s/$HOME/aquilo/"
texto.txt




Ou ainda, para evitar completamente a
interpretação do shell, sem se preocupar com aspas, coloque os
comandos Sed num arquivo. Veja o tópico Colocando comandos Sed num
arquivo. 









Usando outros delimitadores

No comando s

É comum ao fazer um comando de
substituição s/// conter uma / num dos dois lados do comando, como
quando querendo substituir /usr/local/bin por /usr/bin. 





Sendo a barra o delimitador do comando
s as outras barras comuns devem ser escapadas com a barra invertida
\, para não serem confundidas com os delimitadores normais, ficando
o monstro a seguir: 





prompt$ sed
's/\/usr\/local\/bin/\/usr\/bin/' texto.txt




Para evitar ter que ficar se escapando
todas estas barras, basta lembrar que o comando s aceita qualquer
delimitador, sendo a barra apenas um padrão de referências
históricas. Então, neste caso, poderíamos escolher outro
delimitador como por exemplo a vírgula: 





prompt$ sed
's,/usr/local/bin,/usr/bin,' texto.txt




Evitando-se de ter que ficar escapando
as barras. A mesma dica vale para o comando y. 







E se precisássemos apagar as linhas
que contém o /usr/local/bin? Teríamos que colocar o nome do
diretório no endereço do comando d, voltando com a festa dos
escapes:




prompt$ sed
'/\/usr\/local\/bin/d' texto.txt




Para usarmos outro delimitador no
endereço, basta escaparmos o primeiro, que no caso abaixo é a
vírgula: 





prompt$ sed
'\,/usr/local/bin,d' texto.txt




Confusão de delimitadores com o texto
a ser procurado é muito comum de acontecer, então se algo não está
funcionando como deveria, olhe com cuidado para ver se não há
conflitos entre eles. 








Gravando o resultado no mesmo
arquivo

Problema inicial

O procedimento comum quando se quer
gravar num arquivo o resultado de um comando Sed, é o
redirecionamento: 





prompt$ sed 'comando' texto.txt &gt;
texto-alterado.txt




Mas é muito comum, ao alterarmos um
arquivo, queremos gravar estas alterações no próprio arquivo
original. A tentativa intuitiva seria: 





prompt$ sed 'comando' texto.txt &gt;
texto.txt




Mas é só fazer para ver. Além de não
dar certo, você ainda perderá todo o conteúdo do arquivo. 





Isso acontece porque ao fazer o
redirecionamento &gt;, o shell abre imediatamente o arquivo
referenciado, antes mesmo de começar a executar o comando Sed. E
como este é um redirecionamento destrutivo &gt; e não incremental
&gt;&gt;, se o arquivo já existir, ele será truncado, e seu
conteúdo perdido. A essa altura, o Sed começará seu processamento
já lendo um arquivo texto.txt vazio, e aplicados qualquer comandos
Sed num arquivo vazio, o resultado será o próprio arquivo vazio. 







Solução genérica

Para evitar isso, voltamos a primeira
tática de gravar o resultado num outro arquivo, e depois o mais
natural é mover o arquivo novo sobre o original: 





prompt$ sed 'comando' texto.txt &gt;
texto-alterado.txt

prompt$ mv texto-alterado.txt texto.txt




Para a grande maioria dos casos, isso é
suficiente, mas convém aqui lembrar que caso o arquivo 'texto.txt'
possua atributos especiais, grupo diferente do padrão do usuário,
ou referências (links, simbólicos ou não) para outros arquivos,
tudo isso será perdido. Ao mover o arquivo recém-criado, com os
atributos padrão do sistema, sobre o original, este perderá seus
atributos e ficará com os padrões do sistema, herdado do arquivo
novo. 







Solução segura

Para evitar isso, a abordagem mais
ortodoxa e segura seria aplicar o comando Sed numa cópia e gravar o
resultado no arquivo original via redirecionamento: 





prompt$ cp -a texto.txt texto-tmp.txt

prompt$ sed 'comando' texto-tmp.txt &gt;
texto.txt

prompt$ rm texto-tmp.txt




Novamente, isso só é necessário com
arquivos especiais, senão a solução com o mv pode ser usada. Mas é
importante ter em mente esta outra maneira e principalmente saber o
porque de utilizá-la, sendo este conhecimento aplicável a qualquer
outro comando do sistema que leia e grave arquivos. 











Como substituir alguma coisa por
uma quebra de linha

No Sed da GNU, a partir da versão
3.02.80(*), foi adicionado o \n como escape válido dos dois lados do
comando s///. Com isso a tarefa de colocar cada palavra numa linha
isolada, ou seja, trocar espaços em branco por quebras de linha,
fica trivial: 





prompt$ sed 's/ /\n/g' texto.txt




Mas com outras versões do Sed que não
entendem este escape, a quebra de linha deve ser inserida
literalmente e deve ser escapada: 





prompt$ sed 's/ /\

prompt$ /g'
texto.txt




Como curiosidade, a operação inversa,
de colocar todas as linhas de um arquivo numa linha só, já é mais
trabalhosa e utiliza o conceito de laço: 





prompt$ sed ':a;$!N;s/\n/ /g;ta'




(*) veja o tópico Nota sobre os
adicionais GNU 






Apagando linhas específicas

O comando para apagar linhas é o d. 





O único detalhe nesta tarefa é
especificar quais linhas você vai querer apagar. Isso está
completamente coberto no tópico O endereço. 







Como ignorar maiúsculas e
minúsculas

O jeito padrão do Sed ser
"ignore-case", é dizendo literalmente todas as
possibilidades, como em: 





prompt$ sed
'/[Rr][Oo][Oo][Tt]/d' texto.txt




Para apagar todas as linhas que contêm
a palavra root, ROOT, RooT etc. 





No Sed da GNU, a partir da versão
3.01-beta1(*), foi adicionado o modificador I no endereço e no
comando s///, fazendo com que o comando acima fique mais simples: 





prompt$ sed
'/root/Id' texto.txt




Ou ainda: 





prompt$ sed 's/root/administrador/Ig'
texto.txt




(*) veja o tópico Nota sobre os
adicionais GNU 









Lendo e gravando em arquivos
externos

0.4.1. Lendo arquivos

Uma tarefa comum é incluir cabeçalho
e rodapé num arquivo qualquer. O Sed possui um comando específico
para ler arquivos, o r, então basta(*): 





prompt$ sed -e '1r cabecalho.txt' -e
'$r rodape.txt' texto.txt




Para incluir o cabeçalho após a linha
1 e incluir o rodapé após a última linha. 





(*) a explicação do porquê das
opções -e está no tópico Aplicando vários comandos de uma vez. 









Gravando arquivos

O comando w grava num arquivo a linha
atual, ou melhor, o conteúdo do espaço padrão. Por exemplo, você
quer gravar num arquivo o resultado de uma busca por linhas que
contêm a palavra estorvo. A solução não-Sed seria: 





prompt$ grep 'estorvo' texto.txt &gt;
estorvos.txt




Nosso similar em Sed seria: 





prompt$ sed '/estorvo/w estorvos.txt'
texto.txt




Gravar dados num arquivo também pode
servir de espaço auxiliar caso o espaço reserva não seja
suficiente. Mas esta é uma opção drástica, não tão flexível.
Mais informações sobre o espaço reserva no tópico Conhecendo os
registradores internos. 







Trocando um trecho de texto por
outro

Uma tarefa que parece simples mas
confunde, é trocar um trecho de texto, como um parágrafo inteiro
por exemplo, por outro trecho, independente do número de linhas de
ambos. 







Trocar várias linhas por uma

Essa é simples, basta usar o comando
c, que "Coloca" um texto no lugar da linha atual. A única
complicação é definir o endereço, para aplicar o comando apenas
nas linhas desejadas. Por exemplo, vamos colocar uma frase no lugar
de uma área de texto pré-formatado num documento HTML. Esta área é
delimitada pelos identificadores &lt;pre&gt; e &lt;/pre&gt;: 





prompt$ sed '/&lt;pre&gt;/,/&lt;\/pre&gt;/c
\

prompt$ aqui tinha texto pré-formatado'
texto.html




Note que o comando c (assim como o a e
o i) exige que o texto que ele recebe como parâmetro esteja na linha
seguinte, estando a quebra de linha escapada com a barra invertida \ 





No Sed da GNU, a partir da versão
3.02a(*), é permitido que se coloque o texto na mesma linha: 





prompt$ sed '/&lt;pre&gt;/,/&lt;\/pre&gt;/c
aqui tinha texto pré-formatado' texto.html




(*) veja o tópico Nota sobre os
adicionais GNU 







Trocar várias linhas por outras

Similarmente a trocar por apenas uma
linha, pode-se usar o comando c e passar várias linhas para ele. O
único detalhe é que todas as linhas devem ser escapadas no final,
menos a última: 





prompt$ sed
'/&lt;pre&gt;/,/&lt;\/pre&gt;/c \

prompt$ aqui tinha texto
pré-formatado,\

prompt$ mas eu resolvi tirar.\

prompt$ porque?\

prompt$ porque sim' texto.html




É claro, quando o comando começa a
ficar grande desse jeito, é melhor colocá-lo num arquivo. Saiba
mais detalhes sobre isso no tópico Colocando comandos sed num
arquivo. 





Mas melhor ainda é separar o comando
Sed do texto, colocando-o num arquivo separado. Assim, quando se
precisar alterar este texto, basta editá-lo, sem mudar o comando
Sed, e sem precisar ficar colocando \ no final de cada linha. 





Supondo que nosso texto explicativo do
porquê da retirada do texto pré-formatado foi gravado no arquivo
desculpa.txt, utilizaremos o comando r para lê-lo e o comando d para
apagar o texto antigo: 





prompt$ sed -e '/&lt;\/pre&gt;/r
desculpa.txt' -e '/&lt;pre&gt;/,/&lt;\/pre&gt;/d' texto.html




Então acompanhe o que acontece: o
primeiro comando será executado apenas na linha &lt;/pre&gt; que é
o fechamento do trecho, então vamos esquecer dele por enquanto. O
segundo comando diz para apagar o trecho desde &lt;pre&gt; até
&lt;/pre&gt;, então assim que começar o trecho, ele vai apagando,
linha por linha. 





Ao chegar na linha que contém o
&lt;/pre&gt;, o primeiro comando Sed entra em ação e lê o arquivo
desculpa.txt, colocando seu conteúdo imediatamente após a linha
atual. Em seguida, o segundo comando apaga a linha &lt;/pre&gt;,
completando a tarefa. 





Esta segunda solução é mais difícil
de entender e implementar, mas é muito mais prática caso a
alteração do texto a ser colocado seja freqüente, além destas
alterações poderem ser feitas por alguém que nem saiba o que é
Sed, pois será apenas um texto normal. 





Note que sempre que o &lt;/pre&gt; foi
referenciado nos endereços, a barra foi escapada, ficando &lt;\/pre&gt;.
A explicação desse escape está em Usando outros delimitadores. 





obs.: talvez o &lt;pre&gt;&lt;/pre&gt;
não seja um exemplo dos mais didáticos, mas não me veio algo mais
comum à mente... 







Emulando outros comandos

Aqui vão alguns exemplos de emulações
de outros comandos usando-se o Sed: 


 
  
  
  

   
    
comando

   
    
emulação

  

   
    cat

   
   
    sed
    :

   
  

   
    head

   
   
    sed
    10q

   
  

   
    grep

   
   
    sed
    /padrão/!d

   
  

   
    grep
    -v

   
   
    sed
    /padrão/d

   
  

   
    tac

   
   
    sed
    1!G;h;$!d

   
  

   
    tail
    -1

   
   
    sed
    $!d

   
  

   
    tr
    A-Z a-z

   
   
    sed
    y/ABCDEF...UVWXYZ/abcdef...uvwxyz/

   
  

   
    wc
    -l

   
   
    sed
    -n $=