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 >
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 >
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 >, o shell abre imediatamente o arquivo
referenciado, antes mesmo de começar a executar o comando Sed. E
como este é um redirecionamento destrutivo > e não incremental
>>, 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 >
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 >
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 >
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 <pre> e </pre>:
prompt$ sed '/<pre>/,/<\/pre>/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 '/<pre>/,/<\/pre>/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
'/<pre>/,/<\/pre>/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 '/<\/pre>/r
desculpa.txt' -e '/<pre>/,/<\/pre>/d' texto.html
Então acompanhe o que acontece: o
primeiro comando será executado apenas na linha </pre> que é
o fechamento do trecho, então vamos esquecer dele por enquanto. O
segundo comando diz para apagar o trecho desde <pre> até
</pre>, então assim que começar o trecho, ele vai apagando,
linha por linha.
Ao chegar na linha que contém o
</pre>, 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 </pre>,
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 </pre> foi
referenciado nos endereços, a barra foi escapada, ficando <\/pre>.
A explicação desse escape está em Usando outros delimitadores.
obs.: talvez o <pre></pre>
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 $=
|