Slackjeff Wiki

Bits que significam

Ferramentas do usuário

Ferramentas do site


utils:ed

ed

O editor ed é o editor de texto original do UNIX. Ele é um editor de linha de comando :?:, interativo :?:, minimalista :?:, padronizado :?: e multimodal :?:.

Para mais informações, vide características de editores de texto.

Em resumo, o ed é muito versátil, amplamente disponível, aderente ao padrão POSIX (vide Padronização), possui tudo o que um editor de texto precisa (porém não mais do que isso), e é dividido em dois modos: um modo de entrada, para fornecimento de texto, e um modo de comandos, para aplicar comandos de visualização ou de alteração sobre o texto.

Prós e contras

Se você sempre usou editores de texto visuais, certamente vai estranhar um editor como o ed, e talvez se pergunte em que situações ou por quais motivos poderia usá-lo. Essa é uma reflexão interessante. As vantagens e desvantagens deste programa estão fortemente ligadas às características que citei no início desse documento.

Vantagens

Vamos começar falando de algumas vantagens:

  • Histórico de edições visível

Por ser um editor de linha de comando, todas edições feitas anteriormente ficam visíveis no terminal (mesmo depois que ele é encerrado). Saber o que estava sendo feito há pouco pode ser útil na decisão do que fazer a seguir. Isto vale tanto para a edição do documento quanto para o que possivelmente será feito depois.

Por exemplo, você pode editar um script shell e por ter à vista as edições que acabou de fazer, já saber que parâmetros informar ao script no momento de acioná-lo.

  • Poucos detalhes a serem memorizados

Por ser um editor minimalista, há pouco que se precise memorizar sobre o seu funcionamento. Ao aprender a usá-lo, você pode precisar consultar a documentação algumas vezes, mas com o tempo você terá tudo que precisa saber na sua cabeça. Daí para a frente, só precisará da sua criatividade para fazer mais com o ed.

  • Disponibilidade

Por ser um editor padronizado (e também por ser um editor minimalista) é razoável esperar que ele esteja disponível, mesmo em sistemas com recursos mais limitados. Em alguns casos, ele pode ser a sua única (ou ao menos a única conveniente) forma de editar um arquivo. Mesmo que de um sistema para outro a implementação mude, você provavelmente não precisará se preocupar com isso, pois a padronização garante um comportamento consistente entre implementações diferentes.

  • Flexibilidade

Por ser um editor de linha de comando, você pode exibir as linhas na ordem que quiser, pode filtrar apenas um grupo específico de linhas que quer ver, pode redirecionar uma ou mais linhas para um comando e ver a saída do comando, e pode rapidamente avançar e retroceder sobre o texto, sem que para isso todas as linhas entre o ponto A e o ponto B precisem ser exibidas na tela.

  • Integração com o sistema

O editor ed pode fazer uso de comandos do sistema. Além da flexibilidade que isso oferece, também é útil para executar determinadas ações diretamente a partir do editor. Como citado no tópico anterior, pode inclusive direcionar todo ou parte do texto editado para algum comando. O contrário também pode ser feito, ou seja, obter a saída de um comando e escrever essa saída no documento sendo editado. Em outras palavras, o editor pode ser minimalista, mas ele tem todo o sistema à disposição para amplificar suas capacidades.

  • Dispensa combinações de teclas

Por ser um editor multimodal, não é preciso utilizar combinações de teclas usando Ctrl e Alt. O texto não se confunde com os comandos, pois eles são isolados em modos de edição diferentes. Como bônus, os comandos são curtos (apenas um caractere), possivelmente acompanhados de endereços (linhas a editar) e parâmetros. Mais detalhes sobre essa estrutura de comandos serão detalhados adiante.

  • Não possui dependências

Por ser um editor minimalista, o ed não possui outras bibliotecas como dependências (nem mesmo ncurses ou readline). Ele se basta. Para sistemas minimalistas e hardware com capacidades limitadas, isso é ótimo. Além disso, não possuir dependências significa que não há preocupação com ligação dinâmica, que não é suportada por alguns sistemas operacionais, e que mesmo quando é suportado já envolve algumas preocupações com segurança.

Não possuir dependências significa que mesmo em modo monousuário do UNIX ele pode ser utilizado. Não por acaso ele geralmente fica no diretório /bin e não em /usr/bin (em sistemas que ainda levam essa separação a sério).

Melhor dizendo

Cabe destacar que isso é um detalhe de implementação, e que nada impede que alguma implementação do ed possua dependência de bibliotecas externas, e passe a ser disponibilizado dentro de /usr/bin, mas isso não apenas é improvável, como nenhuma das implementações listadas na seção de implementações o faz.

O ponto a ser frisado aqui é que a especificação do ed é simples o bastante para que ele seja implementado sem necessidade de bibliotecas externas e ainda assim consiga atender os requisitos da especificação.
  • Resistente a arquivos binários

Muitos editores reclamam quando você tenta abrir um arquivo binário neles. Eles só leem arquivos textuais. Mas não o ed. Ele consegue ler arquivos binários também (embora não seja necessariamente a melhor ferramenta para fazer isso). Você pode representar caracteres não textuais em forma octal, o que pode ser útil para inspecionar arquivos binários, ou para ler arquivos textuais que em algumas ocasiões possuem trechos não textuais.

  • Zero configuração

Por ser minimalista, o ed dispensa configurações, e já está pronto para uso. O máximo que você pode precisar fazer em termos de configuração é criar um alias com as opções que preferir (que são poucas). Mais a frente eu mostro como fazer isso para configurar um prompt para esse editor, o que tende a ser conveniente.

Desvantagens

Mas nem tudo é perfeito, e existem também algumas desvantagens:

  • Curva de aprendizado

O editor ed não é muito intuitivo. O ideal é ler a documentação antes de usar (o que é uma boa prática, de qualquer modo). Se acostumar com o modo de fazer as coisas nele pode levar algum tempo. Se me perguntar, o esforço inicial se paga no longo prazo, mas não posso negar, leva algum tempo para pegar o jeito.

Além disso, o uso de expressões regulares possui uma importância para esse editor. Expressões regulares facilitam muito as tarefas de pesquisa e edição de linhas, e portanto são um aprendizado a parte necessário para tirar o máximo do editor.

O que talvez seja um efeito colateral positivo disso é que demonstrar proficiência com o ed pode inspirar alguma confiança entre seus pares, afinal mostra que você não quer tudo mastigado e se dispõe a “arregaçar as mangas” quando entende que isso é necessário.

  • Costume com os modos

O multimodalismo também pode confundir às vezes. Você pode,em alguns momentos, se esquecer de que está no modo de comandos e inserir texto, o que emite um erro (a propósito, não tenha medo disso, não possui nenhuma consequência), e em outros pode estar no modo de entrada, e inserir um comando que depois precisará ser apagado. Novamente, isso é fácil de resolver, basta remover a linha onde o comando foi inserido.

Com o tempo, essas confusões se tornam menos comuns e a reparação mais rápida. O uso de um prompt (mais detalhes adiante) também ajuda bastante a reduzir essa confusão.

  • Um arquivo de cada vez

No ed, diferente de muitos editores, não é possível abrir e visualizar vários arquivos ao mesmo tempo. Mas convenhamos, você não estaria editando vários arquivos ao mesmo tempo. Se o que você precisa na verdade é visualizar o conteúdo de outro arquivo antes de tomar uma decisão sobre a edição que vai fazer no arquivo atual, isso pode ser feito no ed, basta usar o comando de execução de comandos para exibir na tela (seja com cat, grep, less ou o que desejar) o conteúdo desse outro arquivo, como será explicado neste guia.

  • Cuidado especial com caracteres de controle

Esse realmente é um problema enjoado. Ao inserir novo texto, você deve ter o cuidado de não inserir caracteres de controle por acidente (geralmente quando pressiona a tecla Ctrl por acidente enquanto digita). O resultado é que a linha visualmente parecerá perfeita, mas no meio dela pode constar algum desses caracteres não imprimíveis (portanto não visíveis).

Eu costumo redobrar a atenção enquanto estou no modo de entrada ou fazendo alguma substituição, para evitar erros desse tipo. Mas vez ou outra pode acontecer. Felizmente, há um comando que mostra a linha de modo inequívoco, convertendo os caracteres não imprimíveis para uma representação octal, e existem também comandos que podem ser usados para fazer essa limpeza. Veremos como fazer isso.

  • Não há quebra de linha automática

Um recurso útil para evitar que as linhas de um arquivo fiquem muito longas é a quebra de linha automática, que após atingir determinado ponto automaticamente quebra o texto para uma nova linha. Esse é um recurso que não existe no ed, infelizmente.

Existem algumas formas de lidar com isso. Uma delas é aprimorar o “olhar clínico” para perceber se uma linha está muito longa. Outro recurso é usar o comando de substituição para quebrar linhas após determinado tamanho (não é perfeito mas ajuda). E ainda outro modo de lidar com isso é escrever a linha (ou as linhas) em um arquivo temporário, e depois ler esse arquivo aplicando um comando de formatação. Todos esses truques serão apresentados mais detalhadamente adiante.

  • Não há autoindentação

Este é outro recurso comum em editores, que infelizmente não existe no ed. Talvez por isso, editar arquivos onde se usa espaços para indentar o código não seja muito confortável. Tabulações tendem a ser mais práticas.

Tirando o máximo

Eu sei que apenas falar nas vantagens e desvantagens ainda é um pouco vago e você pode estar se perguntando como tirar o máximo desse editor. Como aproveitar o máximo os pontos fortes e como minimizar o quanto for possível os pontos fracos?

O intuito desse documento é remover as barreiras iniciais, mas a despeito disso, é importante que você evite comparar o ed com outros editores em um primeiro momento. Procure explorá-lo com a mente aberta e aceitar que no começo você terá pouco traquejo com ele. Isso será uma parte quase inevitável do processo, pois você provavelmente já se acostumou bastante com a edição visual. É natural que haja um “choque cultural”.

Quando usar e quando não usar

Em geral, o ed é mais confortável para editar texto que já existe do que para criar texto novo, do zero. Isso faz dele uma opção mais interessante para editar arquivos de configuração e realizar ajustes em código-fonte. Ele também é uma opção interessante quando você precisa obter saídas de comandos e gravar em um arquivo, ou quando precisa fazer mudanças em um arquivo de script e testá-lo em seguida, sem sair do editor (e até mesmo antes de salvar as mudanças!).

Redigir muitas linhas de texto pode não ser muito confortável e talvez seja melhor deixar essa tarefa para os editores visuais. Ao menos enquanto você não tiver mais prática com esse editor.

Como já foi falado antes, o ed também é interessante para cenários mais restritos, como hardware limitado e sistemas mais minimalistas. Ele também é um editor comum para uso em imagens live de recuperação e para uso em modos de sistema protegidos, que possuem critérios mais rígidos de segurança.

Noções básicas

Para aprender a usar o ed, vamos começar detalhando alguns conceitos comuns da sua terminologia:

  • Buffer

Um buffer é uma área de memória onde será armazenado temporariamente o conteúdo que você vai editar. Se você abrir um arquivo que já existe para edição, uma cópia dele vai ser feita e colocada nesse buffer. Quaisquer edições que fizer estarão apenas no buffer, e apenas no momento em que acionar o comando de escrita para salvar é que essas alterações serão replicadas no arquivo.

Se você encerrar o ed sem acionar esse comando de escrita, as edições que tiver aplicado serão perdidas. No entanto, ele emite um alerta antes, para que você saiba que existem alterações não salvas. Existem comandos específicos para fechar ou limpar o buffer ignorando esse alerta.

  • Comando

Um comando é uma ação a ser aplicada sobre o buffer. Existem comandos para exibir uma ou mais linhas, para alterar a posição atual, para alterar o conteúdo de uma linha, para listar linhas que se encaixam em algum padrão e opcionalmente realizar alguma operação sobre elas, comandos para copiar, mover ou remover linhas, comandos para marcar linhas, etc.

Alguns comandos alteram o modo de edição (isto é, mudam do modo de comandos para o modo de entrada).

  • Endereço

A rigor, um endereço é uma linha. Mas essa linha pode ser referenciada de formas diferentes. Pode ser pelo número da linha, pode ser pela ocorrência de algum texto dentro dela, pode ser por um marcador que foi aplicado a essa linha ou pode ser também pela sua posição em relação a alguma outra linha.

Comandos do ed podem utilizar zero, um ou dois endereços, o que varia conforme cada comandos. Geralmente, um endereço significa que o comando opera sobre uma linha específica, e dois endereços operam sobre várias linhas começando pelo primeiro endereço e terminando no segundo. Por exemplo, mostrar uma ou mais linhas, apagar uma ou mais linhas, etc.

Comandos que usam endereços sempre possuem valores default para o caso de não serem fornecidos.

  • Expressão regular

Vide expressões regulares. O ed utiliza as expressões básicas (BRE) do padrão POSIX.

  • Marcador

Um marcador funciona como uma forma fácil de se lembrar de alguma linha. Isso é útil quando, no lugar de se lembrar do número da linha, for mais fácil se lembrar de uma letra representativa do conteúdo daquela linha. Acredite, depois de navegar por algum tempo em um texto, você facilmente se esquecerá do número de uma linha que possuía algo importante, mas terá facilidade para se lembrar que marcou essa linha com a letra “a” (ou qualquer letra que tiver escolhido).

  • Modo

Conforme já explicado, o ed é um editor multimodal. Um modo representa um estado do editor, que no caso do ed servirá para determinar que o texto inserido seja tratado como comando (no modo de comandos) ou como texto normal (no modo de entrada).

  • Nome de arquivo

Alguns comandos do ed precisam de um nome de arquivo (por exemplo, carregar um arquivo no buffer ou escrever o conteúdo do buffer em um arquivo). Geralmente essa informação é fornecida via parâmetro para os comandos do ed. Caso um comando que opera sobre um arquivo não receba esse parâmetro, ele poderá assumir como default o nome de arquivo previamente informado (caso algum já tenha sido fornecido antes).

  • Parâmetro

Alguns comandos podem (ou devem) ser parametrizados. Parâmetros podem ser um nome de arquivo ou endereço, a depender do que o comando faz e espera como entrada. Diferente dos endereços que sempre possuirão um valor default para cada comando, parâmetros nem sempre podem ser omitidos.

  • Pesquisa

Uma operação de pesquisa buscará um determinado padrão de texto ao longo de todo o buffer, e retornará a linha correspondente à próxima ocorrência, caso haja alguma. Caso não haja, entre a posição atual e o fim do buffer, nenhuma ocorrência, a pesquisa seguirá a partir do início, até a posição atual, e retorna um erro, caso não encontre nada.

Uma operação de pesquisa funciona não apenas como uma forma de encontrar texto, mas também como uma forma de endereço, para que algum comando seja executado. Ao realizar uma pesquisa, você pode aplicar um comando sobre a linha retornada.

Uma pesquisa pode ser seguir o caminho normal, para “a frente” no texto, ou o caminho inverso, “para trás”.

  • Posição

No editor ed existe a noção de uma posição atual dentro do buffer. Um buffer pode ser composto por zero ou mais linhas .

A posição zero é especial, no sentido de que não é propriamente uma posição, mas sim a condição de “antes da primeira linha”, o que pode inclusive servir como endereço fornecido para algumas operações. E é a única posição que existe em um buffer vazio. Com uma ou mais linhas no buffer, a posição nunca poderá ser zero.

A maioria dos comandos alteram a posição atual após sua execução.

  • Prompt

Um prompt é um texto (de um ou mais caracteres) que sinaliza que o editor está à espera de um comando. Como comentei antes, é fácil se confundir entre os modos de comandos e de entrada. O uso de um prompt serve para desfazer essa confusão, deixando explícito que se está no modo de comandos.

Por padrão, o editor ed inicia sem um prompt, mas ele pode ser ativado a qualquer momento, durante a edição, ou mesmo ao iniciar o ed.

  • Redirecionamento

No ed, é possível interagir com outros comandos do sistema, tanto para leitura como para escrita. No caso da leitura, o ed aciona um comando do sistema, e lê a sua saída, que é gravada no buffer, ou seja, a saída do comando executado é redirecionada para o buffer.

Já no caso da escrita, todo ou parte do buffer é redirecionado para algum comando externo, que opera sobre esse conteúdo. Isto é muito útil, por exemplo, para direcionar o conteúdo de um script para o interpretador, e pode ser feito até mesmo sem que ele tenha ainda sido salvo, pois o conteúdo do buffer (e não necessariamente do arquivo) é que estaria sendo fornecido.

  • Substituição

A informação de posição do ed é referente apenas a linhas, mas não existe a noção de navegação dentro de uma linha, avançando ou retrocedendo entre caracteres. Portanto, para alterar o conteúdo de uma linha, é preciso usar comandos de substituição, que pesquisam por um padrão de texto na linha, e então caso encontre esse padrão, substitua por algum outro texto.

É possível alterar uma ocorrência de cada vez, ou várias de uma só vez.

Iniciando e encerrando

Muito bem, já vimos os conceitos principais, agora é hora de um pouco de prática. Vamos começar pelo básico que é iniciar e encerrar o editor ed. A propósito, não saber encerrar um editor pode causar um certo pânico, então vamos tratar disso logo.

Para iniciar o editor ed, vamos começar com um exemplo simples, sem abrir nenhum arquivo, apenas iniciar o editor com um buffer vazio. Você verá uma linha vazia, na qual um comando pode ser inserido. Então você aprenderá o seu primeiro comando agora, o comando q (quit), que encerra o editor. Nessa linha vazia, digite q, e o editor será encerrado.

$ ed
q
$

Ufa! Agora você já sabe encerrar o editor, quando precisar. Mas vou além: o comando q só vai encerrar o editor mesmo se ele não tiver sofrido nenhuma mudança. Se você tiver feito quaisquer alterações no buffer, um alerta será emitido e você terá de usar o comando q novamente para confirmar que deseja encerrar. Outra opção, se tiver conteúdo não salvo que queira descartar é usar o comando Q. Nesse caso nenhum alerta será emitido e ele será prontamente encerrado.

É importante frisar que digitar Ctrl+c não encerra o editor. Sinais de interrupção apenas emitem uma mensagem de erro (veremos mais sobre mensagens de erro e alertas depois).

Agora, um outro ponto que é importante aprender desde cedo é sobre a configuração e uso de um prompt. Como falei antes, ele é um meio de evitar a confusão entre o modo de comandos e o modo de entrada. Para iniciar o editor já com o uso de um prompt você deve usar o parâmetro -p, como a seguir:

$ ed -p '* '
* q
$

Aqui usei um asterisco seguido de espaço, mas fica a seu critério qual texto acha melhor como prompt. Desaconselho o uso de interrogação, pois esse sinal já é usado para emitir mensagens de diagnóstico, e também é útil usar um espaço como último caractere do prompt, para não misturar com o comando.

Para não ter que usar esse parâmetro -p toda vez que iniciar o ed, você pode configurar um alias para o ele (dependendo de qual shell utilizar, pode haver variações na forma de fazer isso):

$ alias ed='ed -p "* "'

Nos exemplos seguintes partirei da premissa de que o alias do exemplo acima está configurado.

Antes de prosseguir, tenho apenas mais um assunto para comentar sobre prompts: você pode, a qualquer momento dentro do editor, desabilitar ou reabilitar o prompt com o comando P:

$ ed
* P
P
* q
$

Para quê desabilitar o prompt? Eu realmente não consigo imaginar um bom motivo, mas caso você tenha se esquecido de configurá-lo previamente, e esteja editando um arquivo, pode ser útil lembrar que ele pode ser ativado a qualquer momento.

Apenas tenha em mente que por padrão o prompt usado é um asterisco (*) sem um espaço depois, o que visualmente pode causar algum desconforto por misturar o prompt ao comando, então é mais interessante configurá-lo antes de iniciar o editor, como apontei antes.

Abrindo e trocando de arquivos

Já vimos como o editor pode ser aberto e fechado, mas e quanto à seleção dos arquivos que serão editados? Como isso é feito? Existem duas possibilidades: uma é informar o nome de um arquivo logo ao iniciar o editor, portanto na chamada do comando ed, passar um arquivo como argumento; outra é abrir o editor com um buffer vazio, e em seguida usar o comando e para selecionar um arquivo.

A título de exemplo, consideremos um arquivo de texto, contendo o texto:

poema_ed.txt
Editor minimalista
nunca te deixa na mão,
seja em PC de batata
ou em PC de milhão.

Veremos exemplos das duas formas, começando pelo primeiro caso (já informar o nome do arquivo ao iniciar):

$ ed poema_ed.txt
85
* 

O que houve aqui? O que é esse 85? Trata-se do número de bytes que foram lidos. Isso não equivale necessariamente ao número de caracteres, pois quaisquer quebras de linha e acentos ou cedilhas também são contabilizados. Mais importante do que saber quantos bytes foram lidos é saber que foram lidos. Você pode entender essa mensagem como uma confirmação de que o arquivo de fato foi carregado no buffer.

Logo em seguida o prompt aparece, e você pode inserir comandos para ler ou editar o arquivo. Mas calma, ainda chegaremos lá. Por hora, vamos ver mais algumas questões relacionadas ao carregamento do arquivo.

Digamos que você iniciou o editor com um buffer vazio. Você pode então usar o comando e para carregar o arquivo. Vejamos:

$ ed
* e poema_ed.txt
85
* 

Simples. O efeito prático é o mesmo. O arquivo é carregado no buffer e estará pronto para leitura e edição, e o nome do arquivo será memorizado.

“Espere! Nome do arquivo memorizado? Como assim?” Já imagino que esteja se perguntando. Lembra-se do conceito de nome de arquivo que expliquei antes? Pois bem, ao carregar um arquivo, o ed memoriza o nome dele, para que futuras escritas no buffer possam ser feitas sem que você precise informar novamente esse nome como parâmetro.

Ao iniciar o editor com um buffer vazio, essa informação não existe. Porém, ao iniciar com um arquivo, ou ao carregar um arquivo, como feito nos exemplos anteriores, essa informação é preenchida, com o nome do arquivo carregado (neste exemplo, poema_ed.txt).

Para visualizar o nome memorizado, usamos o comando f. Um erro será emitido se o nome não estiver preenchido ainda.

$ ed
* f
?
* e poema_ed.txt
85
* f
poema_ed.txt
* 

O sinal de interrogação nesse caso indica que houve algum erro (deixemos a interpretação disso para depois). Mas note que depois de carregado o arquivo, o nome passou a existir (ficou memorizado). Podemos a qualquer momento usar esse mesmo comando f para trocar esse nome. Isso não renomeia o arquivo carregado, apenas sinaliza para o editor ed, que no momento de salvar, ele deverá escrever em outro arquivo.

Retomaremos esse assunto quando tratarmos sobre a operação de escrita.

Você pode, a qualquer momento, usar o comando e para trocar o arquivo carregado no buffer (inclusive para um arquivo que ainda não existe, que será criado ao acionar a operação de escrita).

$ ed poema_ed.txt
85
* f
poema_ed.txt
* e outro_arquivo.txt
36
* f
outro_arquivo.txt
* e nao_existe.txt
?
* f
nao_existe.txt
* 

Apenas lembre-se: se houver quaisquer alterações não salvas, um alerta será emitido. Você pode ignorá-lo e reexecutar o comando, para prosseguir, perdendo as alterações.

Caso já tenha certeza de que não quer salvar nada, pode usar o comando E no lugar de e, para que nenhum alerta seja emitido e o editor imediatamente prossiga com o carregamento do próximo arquivo.

Tanto o comando e como o comando E, quando não recebem nenhum nome de arquivo como parâmetro, apenas recarregam o arquivo no buffer (com ou sem a mensagem de alerta, dependendo de qual deles usar). Isto pode ser útil caso queira apenas recarregar o arquivo descartando todas as alterações que fez, desde a última escrita.

A título de curiosidade, perceba uma similaridade entre os comandos q e e, e entre os comandos Q e E. Os comandos em letra minúscula, por poderem incorrer em perda de alterações emitem um alerta. O equivalente em letra maiúscula desconsidera essa possível perda.

Isto não é por acaso. Há uma consistência na escolha do que cada comando significa, e você ainda perceberá outros exemplos dessa consistência, que não são mera coincidência.

Lidando com erros

Errar é humano, e aprender com os erros também. Porém, é preciso compreender onde errou e porque determinada ação foi um erro. Nisso o ed pode te ajudar. Quando você executa um comando inválido, informa parâmetros inválidos para um comando, ou executa um comando que não faz sentido em determinado contexto ou que pode ter consequências ruins, o ed emite uma mensagem de erro (ou de alerta).

Como já vimos, ele é bem sucinto e apenas exibe um sinal de interrogação, que apenas indica que algo está errado. Mas como saber o que foi? Para isto, podemos usar o comando h, que detalha o último erro, exibindo sua mensagem. Vamos voltar a um exemplo que já vimos (tentar mostrar o nome de um arquivo quando não há nenhum):

$ ed
* f
?
* h
no current filename
* 

Obs.: as mensagens podem ter algumas variações conforme a implementação do ed, e também conforme as configurações de idioma no sistema.

Não é possível ver todas as mensagens de erro anteriores, apenas a mais recente (não que haja muita utilidade em ver todas as mensagens de erro passadas). Pessoalmente, vejo certa elegância nesse estilo de diagnóstico, ou seja, apenas quando solicitado. Mas se você prefere que as mensagens de erro sejam sempre emitidas, pode usar o comando H. Isto fará com que todas as mensagens de erro seguintes sejam exibidas de imediato.

$ ed
* H
* f
no current filename
* e
no current filename
* q
$

Lembra quando falei sobre as mensagens de alerta quando você tenta fechar ou limpar o buffer (comandos q e e) e com isso perder alterações não salvas? Pois bem, o ed usa esse sistema de diagnóstico para mostrar que existem alterações não salvas.

Vamos supor que você tenha um buffer aberto com alterações que aplicou e não salvou. O que acontece se você tentar encerrar o editor? Vejamos:

* q
?
* h
warning: file modified
* q
$ 

Note que na segunda execução do comando q ele prossegue com o encerramento.

Atenção

No ed, há uma diferença sutil entre erros e alertas. Os alertas são emitidos quando você pode perder dados do buffer por fechar o editor ou carregar algum outro arquivo. Como mostrei no exemplo anterior, se você insistir em uma ação desse tipo, o ed entenderá que você sabe o que está fazendo e não entrará no seu caminho.

Mas há uma detalhe que pode te pegar de surpresa: depois que o ed emite o alerta de que o buffer foi alterado, ele considera que o alerta já foi dado. Se você continuar a editar o buffer depois disso e mais tarde resolver fechar o editor, ele não emitirá outro alerta e encerrará.

Portanto, é uma boa prática salvar as modificações feitas logo depois do alerta, caso realmente queira salvar. Mais à frente veremos como modificar um arquivo e como salvar modificações. Quando você salva o conteúdo do buffer o ed volta a considerar que precisa te alertar, se houver mais modificações e você tentar executar outra ação que possa ocasionar perda.

Indo e voltando

Até agora só vimos como abrir um arquivo no ed, mas não fizemos nada, nem leitura nem escrita. Vamos então começar com o básico da leitura, que é usar endereços para avançar e/ou retroceder sobre o texto. A princípio os conceitos podem parecer bem rudimentares, mas eles serão importantes para que depois você possa fazer um uso mais efetivo do editor.

Recapitulando os conceitos, o ed possui uma noção de posição dentro do texto, sendo a posição uma linha específica. Ao abrir o editor, você estará na última linha do buffer, ou na posição zero, se o buffer estiver vazio. Para saber que linha é essa (última ou zero), você pode usar o comando =.

$ ed
* =
0
* q
$ ed poema_ed.txt
85
* =
4
* q
$ 

Isso já é um primeiro passo no senso de localização dentro do texto. Mas como ir para outras linhas? Para isso, usamos endereços que apontam para essas linhas. O mais simples deles é informar o número da linha. Ao informar 1 você irá para a primeira linha. Como você já sabe qual é a última linha, sabe também quais são as linhas possíveis de serem informadas (de 1 a 4 nesse caso).

$ ed poema_ed.txt
85
* 1
Editor minimalista
* 2
nunca te deixa na mão,
* 

Eu sei. Isso não parece muito conveniente, você não quer números de linhas e prompts atrapalhando a leitura do texto. Nem eu. Logo mais vamos ver como mostrar linhas em sequência. Por hora, atenha-se ao fato de que podemos informar um número de linha como endereço, e isso faz com que o editor nos leve a essa linha.

Existem outras maneiras de apontar para uma linha, sem ser pelo seu número. Se usarmos o ponto (.) como endereço, apenas apontamos para a linha atual. No exemplo a seguir continuamos de onde paramos no anterior (linha 2):

* .
nunca te deixa na mão,

Não parece muito útil. Mas novamente, atenha-se ao significado do endereço (linha atual). Existem também o endereço da próxima linha, que denotamos com o mais (+), e o da linha anterior, com o sinal de menos (-).

* +
seja em PC de batata
* -
nunca te deixa na mão,
* 

Você pode especificar ainda algo como “duas linhas à frente” (+2) ou “três linhas para trás” (-3):

* +2
ou em PC de milhão.
* -3
Editor minimalista
* 

Ou seja, você pode quantificar quantas linhas quer avançar ou retroceder.

Além do sinal de menos (-) para voltar, podemos usar também o acento circunflexo (^) para o mesmo efeito. Porém ele não é exatamente um consenso entre implementações, e o padrão trata esse endereço como um recurso opcional, ou seja, ele não é ignorado pelo padrão, mas não é garantido. Eu realmente não vejo vantagem nessa notação, mas ela pode existir em alguns casos, como alternativa ao sinal de menos.

Agora, digamos que em algum momento você já se esqueceu de quantas linhas avançou ou voltou e não sabe o número da linha atual. É possível verificar? Claro! Novamente, usando o comando =, mas especificando que quer ver o número da linha atual (cujo endereço é o ponto).

* .=
1
* 

Olha só! Não é que esse ponto tem utilidade? E isso é só o começo.

Podemos determinar um endereço antes do operador de igual para mostrar a qual linha ele se refere. Por padrão, ele mostra a última, mas se antecedido por um endereço, retorna a linha desse endereço.

Veremos mais detalhes sobre essa estrutura de comandos na seção seguinte. Mas seguindo na explicação sobre endereços, existe também um endereço para a última linha. Para ele, usamos o cifrão ($).

* $
ou em PC de milhão.
* 

Existem ainda outras formas de endereçamento que vamos explorar, mas que dependem da explicação de novos conceitos. Agora já podemos falar um pouco mais sobre os comandos que podemos usar e como tirar melhor proveito desses endereços que aprendemos.

A estrutura dos comandos

Um fato interessante sobre os comandos do ed é que todos eles são apenas uma letra (exceto = e ! que ainda veremos, pois não são letras). São poucos comandos o bastante para que isso seja possível.

A composição desses comandos varia um pouco. Alguns são precedidos por um endereço, outros por dois, e outros por zero. Da mesma forma, alguns aceitam (ou necessitam) um parâmetro, e outros não.

Resumindo:

[endereço[,endereço]]comando[parâmetros]

Comandos que aceitam zero endereços (q, Q, e, E, f, h, H, P, u e !) nunca devem ser precedidos por um endereço, pois isso é tratado como um erro pelo editor.

Os comandos que trabalham com um endereço podem ser precedidos de mais endereços, que serão ignorados, e apenas o último deles será considerado. Os que trabalham com dois considerarão os dois últimos, e ignorarão os demais. Tanto os de um como os de dois endereços possuem valores default para os endereços, caso não sejam fornecidos.

Por exemplo, o comando = usa apenas um endereço (por padrão, o último, isto é, $). Portanto, se nenhum endereço é fornecido, ele usa o último. Se algum endereço preceder o comando, ele usará esse endereço (vimos o exemplo com o endereço da linha atual anteriormente), e se mais endereços forem fornecidos, eles serão ignorados (a não ser que algum deles seja inválido, o que retornará um erro).

$ ed poema_ed.txt
* =
4
* 3=
3
* 1,3=
3
* 3,1=
1
* 5,3,1=
?
* h
invalid address
* 

Mas afinal, por que alguns comandos usam só um endereço, e outros usam dois? Simples: porque um endereço serve para uma linha, enquanto dois endereços definem um intervalo de linhas incluindo a do primeiro endereço, a do segundo, e todas que existirem entre elas. Importante lembrar que nesses casos, o segundo endereço deve representar uma linha de número maior que a do primeiro.

Já vamos ver como trabalhar com dois endereços, mas para isso precisamos apresentar mais um comando, que serve para mostrar o conteúdo de uma linha. Mas já não fizemos isso ao informar os endereços das linhas?

Ao visitar uma linha, você deve ter notado que ela sempre é exibida. Não mencionei esse fato, mas isso ocorre porque a ação padrão no ed é a de mostrar a linha, mas você pode usar um comando explícito para mostrar uma linha, o comando p.

Para uma única linha, tanto faz usar esse comando ou não, pois já é a ação padrão. Ou seja, tanto faz usar o comando 1 ou 1p para mostrar a primeira linha. Agora, quando queremos mostrar um trecho de duas ou mais linhas, aí precisamos desse comando. E ele aceita dois endereços para mostrar um intervalo de linhas. Então agora temos um modo de mostrar todo o conteúdo do arquivo de uma vez, comandos no meio.

$ ed poema_ed.txt
* 1,4p
Editor minimalista
nunca te deixa na mão,
seja em PC de batata
ou em PC de milhão.
* 

Bingo! Agora está ficando mais interessante. Mas como eu falei, ed é uma ferramenta sucinta e elegante, e existe um modo ainda mais prático de mostrar todo o conteúdo do buffer, usando apenas uma vírgula para denotar que o comando se aplica “do início ao fim”.

* ,p
Editor minimalista
nunca te deixa na mão,
seja em PC de batata
ou em PC de milhão.
* q
$ 

O comando ,p é um dos que você mais vai usar para textos pequenos, que cabem por inteiro na tela. Mas existem várias combinações interessantes que podemos fazer para definir intervalos. Por exemplo, podemos usar .,+2 para dizer “da linha atual até a linha duas posições depois”, e -,+ para “da linha anterior até a próxima linha”.

É importante saber que ao mostrar um intervalo de linhas, a posição dentro do buffer será ajustada para a última linha exibida.

$ ed poema_ed.txt
* .,+2p
Editor minimalista
nunca te deixa na mão,
seja em PC de batata
* .=
3
* -,+p
nunca te deixa na mão,
seja em PC de batata
ou em PC de milhão.
* .=
4
* 

Na tabela a seguir, podemos ver mais algumas combinações com seus significados:

Atalho Equivale a Significado
, 1,$ Do início ao fim
,. 1,. Do início até aqui
,3 1,3 Do início à terceira
,+3 1,4 Do início às três próximas
3,+3 3,(.+3) Da terceira até as três próximas
; .,$ Daqui até fim
;3 .,3 Daqui até a terceira
;+3 .,(.+3) Daqui até as três seguintes
3;+3 3,6 Da terceira até as três seguintes

Perceba que no lugar da vírgula podemos usar o ponto-e-vírgula (;) para separar os endereços. Existe uma mudança no significado? Depende. Se especificar dois endereços fixos (os números propriamente), não fará diferença usar a vírgula ou o ponto-e-vírgula. Mas se usar endereços relativos como a linha atual (.) ou a próxima linha (+) por exemplo, aí faz diferença.

A diferença é que na separação por ponto-e-vírgula, o segundo endereço é relativo ao primeiro. Portanto a noção de próxima linha, por exemplo, é em referência ao endereço da primeira linha informada. Então, por exemplo, podemos mostrar a segunda e a terceira linha, como a seguir.

* 2;+p
nunca te deixa na mão,
seja em PC de batata
* 

Note que nesse caso o segundo endereço informado é o próximo em relação ao primeiro (linha 2), portanto 3.

Muito bem, agora você já sabe se localizar no texto, navegar pelas linhas e exibir o conteúdo das linhas, seja uma a uma ou várias de uma vez. Sugiro que tome um tempo para praticar com o que aprendeu até aqui, antes de passar para os próximos tópicos.

Ainda não vimos tudo sobre navegação dentro do buffer e ainda veremos mais alguns comandos que podem tornar a experiência mais confortável, mas por hora, pratique com o que aprendeu com o seguinte pensamento: “se eu só tivesse isso à minha disposição, como me sairia?”

Isso é uma excelente maneira de exercitar a criatividade e descobrir formas não convencionais de fazer operações comuns, que tendemos a ignorar quando temos opções mais fáceis.

Inserindo, adicionando e alterando linhas

Até aqui só tratamos sobre a leitura dos arquivos. Agora vamos começar a falar em escrita. E para isso vamos precisar recapitular a discussão sobre multimodalidade. Como falei, o ed é um editor que possui dois modos, e até aqui só usamos o modo de comandos, ou seja, inserimos um comando e o ed realiza alguma operação.

Agora, vamos começar a ter contato com a troca de modos, mudando do modo de comando para o de entrada, e depois voltando para o de comandos. Os comandos que apresentarei agora são os únicos que realizam essa troca: o comando i para inserção de texto (antes do endereço informado), o comando a para adição de texto (depois do endereço informado) e o comando c para alteração de texto (substituindo uma ou mais linhas, conforme o endereço ou endereços fornecidos).

Quando acionados, esses comandos iniciam o modo de entrada (no qual não haverá prompt), e todo o texto que você digitar será tratado como texto a ser inserido no buffer. Para terminar, basta inserir um ponto na última linha (sem nada antes ou depois).

Esses três comandos, como falei antes, possuem um endereço padrão (o da linha atual). Caso o buffer esteja vazio, a posição atual é zero (o que é um valor aceitável para a e i, mas não para c). Quando usamos o endereço zero, os comandos a e i tem exatamente o mesmo efeito. Só percebemos a diferença quando usamos um endereço “real”.

Vejamos um exemplo:

$ ed
* i
primeira linha
.
* 0a
não, essa é a primeira linha
.
* ,p
não, essa é a primeira linha
primeira linha
* 

Perceba que depois dos comandos i ou a, não há prompt. Todas as linhas seguintes são tratadas como texto normal. Quebras de linha são preservadas. Somente quando uma linha com um ponto isolado é fornecida que se retorna para o modo de comandos.

Perceba também que nos dois casos (isto é, tanto ao inserir como ao adicionar) o conteúdo da primeira linha foi o último fornecido. Isso acontece porque inserir antes ou depois da posição zero de qualquer modo vai inserir antes da primeira linha do buffer. Portanto os comandos 0i, 1i e 0a fazem exatamente a mesma coisa na prática.

Já os comandos 1i e 1a diferem no fato de que 1i insere antes da primeira linha, mas 1a insere o texto depois da primeira linha.

* 1
não, essa é a primeira linha
* a
segunda linha
.
* ,p
não, essa é a primeira linha
segunda linha
primeira linha
* 

Neste exemplo eu poderia ter usado 1a diretamente em vez de separar em dois comandos 1 e a. Apenas quis frisar que a atua sobre a posição atual, que neste exemplo é 1.

Vejamos agora o comando de alteração, c. Por padrão, como eu disse, ele atua sobre a posição atual, substituindo o conteúdo dessa linha pelo texto que será fornecido (podendo ele ter uma ou mais linhas).

* 3
primeira linha
* c
terceira linha
.
* ,p
não essa é a primeira linha
segunda linha
terceira linha
* 

Aqui, do mesmo modo poderia ter usado 3c diretamente no lugar de ir para a linha 3 primeiro e só substituir depois. Assim como o comando p que vimos antes, c pode usar um ou dois endereços. Se forem dois, ele substitui todas as linhas desde o primeiro endereço até o segundo.

* ,c
Nada do que estava escrito antes importa.
Apaguei tudo e agora o texto será outro.
.
* ,p
Nada do que estava escrito antes importa.
Apaguei tudo e agora o texto será outro.
* 

Percebeu o que aconteceu aqui? Usei apenas uma vírgula para definir dois endereços (recapitulando, uma vírgula equivale a 1,$, isto é, do início ao fim), portanto podemos usar esse comando para apagar todo o arquivo e fornecer outro conteúdo no lugar dele.

Esses três comandos (a, i e c) são os únicos que operam no modo de entrada. Todos os demais operam exclusivamente no modo de comandos.

Persistindo mudanças

Depois de fazer modificações em um buffer em algum momento você eventualmente vai querer salvar essas modificações para não perdê-las quando fechar o editor. Para isso usaremos o comando w. Mas atenção, para que ele funcione, ele precisa de um parâmetro. Lembra-se de quando falei na estrutura dos comandos que alguns usam parâmetros? Esse comando é mais um deles.

Novamente, o parâmetro será um nome de arquivo, o arquivo onde você quer escrever. Do mesmo modo como vimos com os comandos e, E e f, o comando w possui um valor default para esse parâmetro, caso o editor já esteja trabalhando sobre algum arquivo, isto é, se você abriu o editor ed informando algum arquivo ou carregou algum arquivo com e.

Caso contrário, ele ainda não possui um nome de arquivo memorizado e tentar escrever sem informar esse parâmetro será um erro. Vamos continuar no exemplo da seção anterior, onde estávamos trabalhando sobre um buffer anônimo.

* w
?
no current filename
* w qualquer_coisa.txt
84
* f
qualquer_coisa.txt
* q
$ 

Veja que precisei informar um nome de arquivo (que foi criado ao escrever o buffer). Eu poderia fornecer o nome de um arquivo que já existe, mas nesse caso o conteúdo do arquivo informado seria perdido e substituído pelo conteúdo do buffer (o que não necessariamente é o que eu queria), então é bom ter um pouco de cuidado ao escrever, e prestar atenção onde está escrevendo.

Se estiver com receio de escrever um arquivo por não ter certeza se ele já existe, há um modo de verificar isso. Mas isso é assunto para outra seção, onde trataremos execução de comandos.

Outro detalhe a comentar sobre essa operação é que, assim como acontece quando carregamos um arquivo no buffer, quando escrevemos o conteúdo do buffer em um arquivo, recebemos uma mensagem informando o número de bytes escritos (nesse exemplo, 84). Novamente, isso também funciona como um indicativo de que a operação deu certo.

Note que depois de escrever, o nome de arquivo ficou memorizado. Mas convém lembrar que se já existia um nome de arquivo memorizado antes, escrever em outro arquivo não vai alterar o nome atual.

$ ed qualquer_coisa.txt
84
* w backup.txt
84
* f
qualquer_coisa.txt
* 

Como pode perceber, isso é útil para salvar um backup, e continuar editando o arquivo atual. Caso você realmente queira alterar o nome do arquivo atual para outro, pode usar o próprio comando f para isto:

* f outro_arquivo.txt
* w
84
* q
$ 

Veja que após alterar o nome do arquivo, o comando w foi usado sem parâmetros. Isso escreveu em outro_arquivo.txt, pois este era o nome memorizado no momento da escrita.

Agora, lembra-se do que falei sobre os alertas no ed? Vamos recapitular agora que já conhecemos o comando w. Ao aplicar mudanças no buffer, o ed considera que há mudanças não salvas. Se você tentar fechar normalmente o editor (isto é, com q) ou carregar normalmente outro arquivo (com e), ele emitirá um alerta e considerará que o alerta já foi dado.

Pois bem, no momento em que você escrever o conteúdo do buffer em um arquivo (seja o arquivo atual ou outro), ele passa a considerar que não há mais nenhuma mudança não salva (ou seja, que é seguro encerrar ou carregar outro arquivo), e a partir desse ponto, para qualquer alteração que você fizer o ed vai considerar que você ainda não foi alertado, caso tente usar q ou e, e vai emitir o alerta novamente.

Mais comandos de visualização

Até aqui você só utilizou o comando p para ver o conteúdo das linhas (seja explicitamente, ou implicitamente, apenas fornecendo um endereço para navegar). Além dele, existem outros dois comandos que também servem para mostrar o conteúdo das linhas, mas de outras formas.

O primeiro deles é o comando n, que mostra o conteúdo da linha (igual p), mas precedido pelo número da linha.

ed poema_ed.txt
* ,n
1       Editor minimalista
2       nunca te deixa na mão,
3       seja em PC de batata
4       ou em PC de milhão.
* 

Se filtrarmos por um conjunto de linhas, a numeração não é afetada.

* 3,4n
3       seja em PC de batata
4       ou em PC de milhão.
* 

Como você pode imaginar, esse é um recurso valioso para edição de programas e arquivos de configuração. E ele também é útil para se localizar em um arquivo. Antes falamos do comando = que pode ser usado para isso. Mas talvez digitar .= seja menos prático do que apenas n, que ainda tem o benefício de mostrar o conteúdo da linha atual.

Agora vamos ver o outro comando, l (letra éle). Ele mostra o conteúdo das linhas de forma inequívoca.

O que quer dizer “inequívoca” nesse caso? Quer dizer que todos os caracteres estarão visíveis e você saberá exatamente quais são, sejam eles imprimíveis (como letras, números, pontuações, etc) ou não (caracteres de controle, espaços, tabulações, fim de linha, etc). Vejamos na prática:

* ,l
Editor minimalista$
nunca te deixa na m\303\243o,$
seja em PC de batata$
ou em PC de milh\303\243o.$
* 

Percebeu a diferença? No lugar de uma apresentação bonitinha, temos uma outra mais feia, mas também mais reveladora. Primeiro, para cada fim de linha temos um sinal de cifrão ($) para deixar evidente que ali há um caractere de fim de linha, que é um caractere não imprimível (também muitas vezes expresso como \n em algumas representações).

Caracteres imprimíveis do conjunto ASCII são apresentados sem nenhuma alteração, mas caracteres fora desse conjunto (como letras com til, acentos, cedilha) e também os caracteres de controle são apresentados na notação \ddd (onde cada d é um dígito octal).

Portanto, podemos saber cada um dos bytes que está impresso no buffer pela sua representação octal, caso não seja imprimível ou não seja ASCII. E ainda, tabulações são representadas pela sequência de caracteres \t, para que possam ser distinguidas de espaços.

E para quê isso é útil? As funções disso variam bastante. Por exemplo, você deve se lembrar quando citei nas desvantagens do ed a possibilidade de acidentalmente incluir caracteres de controle. Pois bem, se você usar o comando p para mostrar uma linha, esses caracteres ficam ocultos.

Porém, com o comando l você verá que esses caracteres estão ali, e saberá que precisa fazer uma correção. Como? Existem formas diferentes. Por enquanto você pode usar um comando que já aprendeu, o comando c, que substitui o conteúdo de uma linha por uma ou mais outras linhas. Essa normalmente não é a forma mais confortável de fazer essa correção, e mais adiante nesse tutorial vamos ver outra maneira mais conveniente.

Mas o comando l não serve só para isso. Como eu disse também, nas vantagens do ed, esse editor é resistente a conteúdo binário. Ou seja, você pode abrir arquivos binários nele (como um arquivo de imagem, por exemplo), que ele não vai reclamar. E pode mostrar todo o conteúdo do arquivo usando o comando l. Isso é conveniente pois assim você não polui a tela com caracteres “estranhos” como tipicamente ocorre ao tentar mostrar conteúdo binário na tela do terminal.

Ao editar arquivos gravados em codificações diferentes (por exemplo, em ISO-8859-1 em um sistema que espera UTF-8), esse comando também poderá ser útil, pois mostrar caracteres de uma codificação diferente do esperado também pode resultar em caracteres não imprimíveis ou símbolos inesperados aparecendo na tela.

E ainda, você pode eventualmente trabalhar com arquivos que sejam majoritariamente textuais, mas que tenham dentro deles partes compostas por arquivos binários. Isso acontece por exemplo em arquivos no formato SVG com imagens rasterizadas embutidas, ocorre às vezes em programas escritos em Shell com programas binários no final.

Portanto, o ed pode ser muito útil como uma ferramenta de inspeção de arquivos.

Ainda outra situação onde esse comando vem a calhar é quando você quer saber se existem espaços no final da linha. O comando p não mostra isso, mas com o comando l você consegue saber, pois vão aparecer os espaços antes do cifrão (que representa o final da linha).

Um ponto importante a saber sobre os comandos de apresentação (p, n e l) é que eles podem ser combinados com vários dos outros comandos do ed (e inclusive entre eles mesmos).

* ,nl
1       Editor minimalista$
2       nunca te deixa na m\303\243o,$
3       seja em PC de batata$
4       ou em PC de milh\303\243o.$
* q
$ 

Nesse exemplo, ao usar n e l de uma só vez, vemos as linhas numeradas e em apresentação inequívoca.

Veremos depois que anexar comandos de apresentação a outro comando é útil em operações de pesquisa e substituição de texto, para mostrar uma linha pesquisada ou o resultado de alguma alteração aplicada.

Pesquisa de texto

Vamos explorar mais um tópico relacionado à navegação pelo buffer, a pesquisa de texto. Por enquanto, estávamos navegando apenas por endereços absolutos (número da linha) e relativos (próxima, anterior, última). Agora veremos mais um tipo de endereço, a pesquisa.

O arquivo poema_ed.txt nos foi útil até aqui, mas agora vamos tomar outro exemplo, que nos será mais conveniente para explorar esse tópico, um arquivo-fonte C. Não se preocupe se não souber programar em C, nosso objetivo aqui será explorar as funcionalidades do editor ed.

area.c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define QUADRADO(x) ((x)*(x))

double
area_do_circulo(double r)
{
        return 4.0 * M_PI * QUADRADO(r);
}

int
main(int argc, char **argv)
{
        if (argc < 2) {
                printf("Uso: %s raio\n", argv[0]);
                exit(1);
        } else {
                double raio = atof(argv[1]);
                double area = area_do_circulo(raio);
                printf("%g\n", raio, area);
        }

        return 0;
}

Podemos usar o editor ed para pesquisar por termos específicos dentro do texto, e ao encontrar, ir para a linha que possui aquele termo. Para pesquisar, incluímos o termo entre duas barras. Por exemplo, vamos pesquisar pelo termo QUADRADO dentro desse programa.

$ ed area.c
492
* /QUADRADO/
#define QUADRADO(x) ((x)*(x))
* 

Depois que você faz uma pesquisa, se o termo é encontrado, sua posição no buffer passa a ser a da linha que possui o termo. Isso significa que se você fizer novamente a mesma pesquisa, vai pular para a próxima ocorrência, se houver. Caso não, permanece no mesmo lugar.

Você não precisa digitar o termo de novo, pode apenas usar uma única barra para indicar que quer refazer a última pesquisa:

* /
        return 4.0 * M_PI * QUADRADO(r);
* 

Se você estiver no meio do arquivo e fizer uma pesquisa, caso não encontre nenhuma ocorrência até o fim do arquivo, a sua posição no buffer volta para o início do arquivo, e de lá continua procurando, até encontrar alguma ocorrência ou alcançar o local onde estava quando fez a pesquisa.

Do mesmo modo que você pode pesquisar para a frente no arquivo (isto é, do início para o fim), também pode fazer uma busca reversa (do fim para o início), usando a interrogação (?) no lugar da barra (/).

* ?QUADRADO?
#define QUADRADO(x) ((x)*(x))
* ?
        return 4.0 * M_PI * QUADRADO(r);
* 

Nesse exemplo, usei o comando ?QUADRADO? para fins de demonstração, mas poderia ter digitado apenas ? para voltar já na primeira busca reversa. Depois que realiza uma pesquisa por um termo, você pode repetí-la em qualquer sentido.

Outra abreviação que pode ser feita é omitir a segunda barra (ou interrogação) após o termo. Isso é permitido quando só o que você quer é saltar para a linha que possui o termo e mostrar o conteúdo da linha.

* /QUADRADO
#define QUADRADO(x) ((x)*(x))
* 

Mas lembre-se: uma pesquisa funciona como um endereço, e para especificar um comando diferente do padrão (p), é necessário informar a segunda barra (ou interrogação), seguida do comando a ser aplicado. Por exemplo, se quiser pesquisar e mostrar a linha numerada:

* /area_do_circulo/n
8       area_do_circulo(double r)
* 

Podemos deixar isso ainda mais interessante, usando pesquisas para mostrar várias linhas de uma só vez, de um ponto A até um ponto B. Por exemplo, podemos querer mostrar a estrutura e conteúdo da função area_do_circulo. Se notar, aqui estamos usando a convenção de que uma linha antes do nome da função temos o tipo (nesse caso double) de retorno, e o fim da função é dado por um fechamento de chaves.

Logo, podemos mostrar o seu conteúdo como a seguir:

* /area_do_circulo/-,/}/n
7       double
8       area_do_circulo(double r)
9       {
10              return 4.0 * M_PI * QUADRADO(r);
11      }
* 

Talvez pareça confuso a primeira vista, mas vamos decifrar por partes: primeiro definimos uma pesquisa pelo termo area_do_circulo (que é o nome da função que buscamos), depois um sinal de menos (-) para indicar que na realidade buscamos a linha anterior a desse termo (onde está o tipo da função, nesse caso double), depois a vírgula que indica que logo depois vem outro endereço. E esse outro endereço por acaso é outra pesquisa, pela linha que denota o término da função (aqui pesquisando pelo caractere de fecha-chaves, }), e por fim o comando propriamente (n) para mostrar as linhas numeradas.

Legal, não?

E aqui vale lembrar, podemos usar pesquisas como um endereço normal, para qualquer comando que requeira um ou dois endereços. No caso de usar dois endereços, pode ocorrer de os dois serem uma pesquisa (como mostramos acima), assim poderia ser apenas um, ou nenhum. A escolha de como se referir a uma linha depende do que fica mais prático.

Expressões regulares

Talvez esse último exemplo que mostrei, do corpo da função tenha te deixado com a pulga atrás da orelha, e se perguntando algo como “mas e se existisse outro fecha-chaves dentro da função, para um bloco if por exemplo?”

Isso realmente comprometeria nosso exemplo, pois a pesquisa ia parar nele, e não veríamos o restante da função. É aí que entra a nossa capacidade de definir algumas regras dentro da pesquisa, para garantir que vamos encontrar exatamente aquilo que queremos. Pesquisar apenas um fecha-chaves pode retornar qualquer um, mas eu poderia determinar que quero especificamente o fecha-chaves que está isolado na linha, ou seja, início da linha, seguido do fecha-chaves, seguido do fim da linha.

Como? Vejamos a seguir:

* /^}$/
}
* 

Mas o que foi isso? Se você já usou expressões regulares, sabe que os caracteres ^ e $ funcionam como âncoras, respectivamente para o início e para o fim da linha.

Não está no escopo desse tutorial ensinar a usar expressões regulares, mas atente-se aos exemplos, pois eles serão comentados (e depois busque entender como funcionam as expressões regulares, especialmente as BREs (do padrão POSIX), que são usadas no ed.

Do mesmo modo que essas expressões podem restringir o escopo da nossa pesquisa, também podem ampliar. Podemos fazer mais do que buscas literais, pesquisando por classes de caracteres (letras maiúsculas, minúsculas, dígitos numéricos decimais ou hexadecimais, caracteres de pontuação, etc.) ou combinações dessas classes, de modo que uma mesma pesquisa pode servir para encontrar vários termos diferentes, mas que se encaixam nas regras definidas.

Vejamos um exemplo simples: se eu quiser buscar quaisquer diretivas de pré-processamento do código em area.c (portanto, as linhas iniciadas em #), eu poderia fazer a seguinte pesquisa:

* /^#.*
#include <stdio.h>
* /
#include <stdlib.h>
* 

Note que aqui pesquisei por qualquer linha que tenha o caractere # logo no início, seguido de .* que significa qualquer quantidade (*) de qualquer coisa (.). Talvez “qualquer coisa” seja um critério muito abrangente. Poderíamos, por exemplo, determinar que o caractere de cerquilha (#) seja seguido de caracteres alfabéticos (usando a classe alpha do POSIX).

* /^#[[:alpha:]]*
#include <math.h>
* 

Mesmo assim, “qualquer quantidade” também é muito abrangente (até porque esse critério admite zero caracteres). Então eu poderia ser um pouco mais pedante e definir a seguinte expressão:

* /^#[[:alpha:]]\{1,\}
#define QUADRADO(x) ((x)*(x))
* /
#include <stdio.h>
* q
$ 

Se estivéssemos usando as expressões regulares estendidas, poderíamos usar um + no lugar do *. Mas isso não existe nas expressões regulares básicas, então o equivalente seria esse \{1,\}, que quer dizer, “ao menos um”. Os caracteres de abrir e fechar chaves nesse caso precisam ser “escapados” (com a barra invertida, \), pois do contrário seriam interpretados literalmente (como inclusive já vimos no tópico anterior).

Eu friso essas diferenças das expressões básicas pois mesmo pessoas que já estão acostumadas a usar expressões regulares com frequência se confundem ao usar esse “dialeto”, que é bem menos comum de ser usado.

Antes que me esqueça de dizer, agora você tem mais um recurso para lidar com os caracteres de controle acidentais: existe a classe cntrl do POSIX, que abrange caracteres desse tipo, e você pode pesquisar por eles para identificar linhas que precisam de correção. Isso será ainda mais útil quando você aprender a usar o comando de substituição de texto, logo mais.

Tenha em mente que a classe cntrl abrange caracteres de tabulação, os quais você pode querer manter. Portanto, pesquisar por caracteres nessa classe em um arquivo que possui tabulações pode resultar em alguns falsos positivos.

De modo geral, expressões regulares são um recurso muito poderoso, mas não tenha pressa em tirar o máximo disso, pois usar expressões equivocadas muitas vezes tende a irritar. Comece usando aquilo que tem mais certeza de que vai funcionar, e conforme se familiarizar, você pode recorrer a expressões mais complexas.

No ed, as expressões regulares são usadas não apenas em pesquisas comuns, como vimos até aqui, mas também em algumas outras ocasiões, como comandos de substituição de texto e comandos globais.

Substituindo texto

Vamos voltar a falar sobre comandos que alteram o conteúdo do buffer. Já tínhamos visto o comando c, que altera todo o conteúdo da linha (ou das linhas) pelo texto fornecido. Isso resolve muita coisa, mas às vezes pode ser pouco prático. Em alguns casos, você só quer substituir alguns caracteres dentro de uma linha, um ajuste mais pontual.

Nesse caso, usamos o comando s, que opera “dentro” das linhas. No ed, ao contrário de editores de texto visuais, não existe a noção de posição do cursor dentro de uma linha. Não avançamos ou retrocedemos entre caracteres de uma linha, pois a noção de posição está restrita a linhas.

Porém, com o auxílio de expressões regulares, podemos determinar qual (ou quais) caracteres serão editados dentro da linha. Podemos usar uma expressão que abrange toda a linha também (^.*$), mas nesse caso faria mais sentido usar o comando c, provavelmente.

A estrutura do comando s é um pouco diferente dos comandos que vimos até aqui. Nele, informamos primeiro um termo a ser pesquisado dentro da linha, depois o termo que o substituirá (podendo ser vazio), e ao fim podemos anexar um comando de visualização (p, l ou n), para ver o resultado da substituição.

Esses três argumentos (termo pesquisado, texto substituto e comando de visualização) são geralmente separados por barras. Então temos a seguinte estrutura:

[endereço[,endereço]]s/pesquisa/substituto/comando

O comando s é muito versátil. Ele pode ser usado tanto para adicionar conteúdo novo (sem remover nada), substituir algo, ou apenas remover uma parte da linha (sem adicionar nada no lugar). Vejamos um exemplo de remoção. Se reparar no programa area.c (e tiver conhecimento de C), há uma linha com um erro lógico, a linha 22. Nela, temos uma função printf com um argumento sobrando (raio). Vamos tratar de arrumar isso.

$ ed area.c
* 22s/raio, //p
                printf("%g\n", area);
* 

O que aconteceu aqui? Na linha 22, usei o comando s para procurar pelo termo “raio, ” (note o espaço ao fim), e substituí-lo por nada (daí o motivo de haver duas barras consecutivas), e por fim uso o comando p para mostrar o resultado da substituição.

E se eu quiser substituir várias linhas de uma vez só, é possível? Sim. Por exemplo, talvez eu pense que o nome da função area_do_circulo é mais longa do que o necessário e queira trocar todas as suas ocorrências por area_circulo. Simples:

* ,s/area_do_circulo/area_circulo
                double area = area_circulo(raio);
* 

“Mas pode isso?” Claro que pode. As linhas que não possuem o termo area_do_circulo apenas ignoram a instrução, enquanto as que possuem fazem a alteração instruída. Note que eu omiti o comando de visualização, e ao omitir também a barra que antecede o comando, o ed entende que deve executar a ação padrão (p), porém apenas a última linha alterada é exibida.

Lembrando mais uma vez, a vírgula que antecede o comando, na prática equivale a 1,$. Eu poderia definir qualquer intervalo de linhas para passar por esse processo, não necessariamente o buffer inteiro.

Enfim, vamos salvar o arquivo com as modificações que fizemos até aqui.

* w
480
* q
$ 

Se eu quisesse mostrar a alteração de cada uma das linhas, seria possível também, mas para isso teria que usar um comando global, assunto que ainda vamos explorar.

O comando s é um grande aliado no momento de remover os caracteres de controle inseridos acidentalmente:

$ ed
* a
asdf^[32 fdsa
.
* ,p
asdf2 fdsa
* ,l
asdf\03332 fdsa$
* ,s/[[:cntrl:]]*//g
* ,l
asdf32 fdsa$
* ,p
asdf32 fdsa
* Q
$ 

No comando de substituição demonstrado acima, removemos quaisquer ocorrências de quaisquer caracteres de controle (classe cntrl), em qualquer quantidade, e ao fim do comando usamos o sufixo g, que significa que a substituição será refeita para toda ocorrência dentro da linha (do contrário, a substituição se limitaria à primeira ocorrência de cada linha).

Cuidado!

Como alertei antes, caracteres de tabulação se enquadram na classe cntrl, portanto evite fazer esse tipo de substituição irrestrita quando houver tabulações que queira preservar.

Nestes casos será mais interessante fazer substituições pontuais. Comandos globais interativos (explicado mais adiante) também podem ajudar nesses casos.

Note no exemplo acima que após a substituição, os caracteres de controle não aparecem nem mesmo após exibir a linha de modo inequívoco, com ,l.

Do mesmo modo, o comando de substituição pode servir para eliminar os espaços sobrando no fim da linha:

$ ed
* a
essas linhas    
terminam com      
vários espaços  
sobrando      
.
* ,l
essas linhas    $
terminam com      $
v\303\241rios espa\303\247os  $
sobrando      $
* ,s/[[:blank:]]*$//
* ,l
essas linhas$
terminam com$
v\303\241rios espa\303\247os$
sobrando$
* Q
$ 

Agora, usamos a classe blank para remover quaisquer espaços vazios em qualquer quantidade, que antecedam o fim da linha. Nesse caso não é necessário usar o sufixo g, pois a pesquisa está ancorada no fim da linha ($), isto é, só há um fim de linha por linha. E nesse caso é importante que haja essa âncora, pois do contrário removeria todos os espaços no arquivo, independente da posição.

No lugar do sufixo g, podemos usar também um número inteiro, caso quiséssemos substituir a n-ésima ocorrência (caso haja; se não houver, um erro é retornado), onde N é o número informado.

$ ed
* a
abra kadabra abra-te sésamo
.
* s/abra/obra/2
* ,p
abra kadobra abra-te sésamo
* Q
$ 

Ainda sobre substituições, lembra-se quando eu disse que a estrutura do comando s geralmente é separada por barras? Pois então, geralmente mas nem sempre. Se você precisar substituir algum texto que possua barras dentro dele, talvez fique desconfortável ou confuso.

Imagine, por exemplo, substituir um texto que representa caminhos em um sistema de arquivos (sendo que no UNIX os diretórios são separados por barras). Facilmente você se confundiria, ou na melhor das hipóteses, teria que adicionar uma barra invertida para cada uma das barras do texto, para que sejam tratadas literalmente.

Por isso, podemos usar outros caracteres no lugar da barra. Por exemplo, você pode preferir, em alguns casos, usar o caractere pipe (|), ou talvez um ponto de exclamação (!).

Exemplo:

$ ed
a
/caminho/para/meu/arquivo
.
* s!caminho/para/!!p
/meu/arquivo
* Q
$ 

Agora imagine se você tivesse que executar o comando:

s/caminho\/para\///p

No mínimo, é desconfortável, não acha? E pra piorar, ainda pode cometer erros, então nesse caso pode ser uma boa trocar a barra por outro caractere.

Usando grupos de expressões

Expressões regulares podem usar parênteses para agrupar partes da expressão (nas expressões básicas do POSIX, os parênteses precisam ser prefixados com uma barra invertida para serem interpretados nesse sentido). O conteúdo dos parênteses, passa então a ser alvo de uma captura, isto é, o valor é salvo para uso posterior.

Nos comandos de substituição do ed podemos tirar proveito disso para utilizar o conteúdo capturado como termo de substituição (ou parte dele). Para se referir a uma captura, basta usar uma barra invertida seguida do número da captura (números vão pela ordem de captura). Uma boa forma de identificar a captura é pela ordem da abertura de parênteses.

Podemos usar esse recurso para, por exemplo, inverter expressões:

$ ed
* a
fulano de tal
.
* s/^\([[:alpha:]]*\) \(.*\)/\2 \1
de tal fulano
* Q
$ 

Há um limite, porém. Só podemos usar até 9 capturas dessa forma. Não que seja muito confortável abusar desse recurso.

Se no lugar de capturar apenas parte da pesquisa, quiser capturar a pesquisa inteira para reutilizar no texto de substituição, não precisa usar um grupo, pois existe um atalho para essa captura, usando “e comercial” (&).

$ ed
* a
Título
.
* s/Título/&:
Título:
* Q
$ 

Note que nesse exemplo reaproveitei toda a expressão (“Título”), adicionando os dois-pontos no final. Mais prático que os parênteses, não acha?

Reutilizando pesquisas e substituições

Do mesmo modo que usar o endereçamento de pesquisa (/) sem informar o termo pesquisado repete a última pesquisa, podemos também reutilizar a última pesquisa feita dentro de um comando de substituição e novos comandos de substituição (ou novas pesquisas também). O que ocorre é que independente de onde você fizer a pesquisa, ela fica salva para reuso posterior.

$ ed
* a
print 'olá'
print 'como vai?'
.
* /olá
print 'olá'
* s//oi
print 'oi'
* 

Além da pesquisa, podemos reaproveitar também o último termo substituto utilizado. Para isso, basta usar um sinal de porcentagem (%) como termo substituto.

* s/print/io.write
io.write 'oi'
* +s//%/p
io.write 'como vai?'
* Q
$ 

Veja que nesse caso reaproveitei as duas coisas: a pesquisa e a substituição.

Outro ponto importante a destacar sobre o reaproveitamento de pesquisas é que se a pesquisa utilizava grupos de captura (expressões entre \( e \)), então as referência a esses grupos também podem ser reaproveitadas na substituição.

Desfazendo e refazendo

Vez ou outra, você pode executar alguma alteração no buffer e logo em seguida perceber que não era aquilo que queria. Isso é especialmente comum em comandos de substituição. Basta uma expressão regular mal formulada para substituir o trecho errado, por exemplo.

Por esse motivo, existe um comando que desfaz a última alteração, o comando u. É sempre bom, depois de aplicar alguma alteração no buffer conferir se o resultado é mesmo o esperado. Lembra-se quando falei que você pode anexar comandos de visualização a vários outros comandos? Pois então, para essa finalidade isso é bem útil (especialmente em comandos de substituição).

O comando u tem uma particularidade: se você executá-lo de novo (sem ter feito alguma outra alteração no buffer), ele refaz a alteração desfeita.

$ ed poema_ed.txt
85
* 1
Editor minimalista
* s/minimalista/simples
Editor simples
* up
Editor minimalista
* up
Editor simples
* 

Perceba que o comando p foi usado como sufixo em u nesse caso, para desfazer e já mostrar o resultado (e depois para refazer e mostrar o resultado).

Essa forma de funcionar do comando u pode surpreender usuários acostumados com editores que desfazem várias ações em sequência. No ed, apenas a última alteração pode ser desfeita. Portanto, é bom ter cuidado com as alterações que aplica, salvar o arquivo de tempos em tempos, após alterações importantes, e a depender da criticidade do arquivo, possuir backups.

Recapitulando, se você fez várias alterações em um buffer (carregado a partir de um arquivo), e de repente decide que quer desfazer todas elas, isso é possível. Basta usar o comando E, que recarrega o arquivo no estado inicial. Vejamos um exemplo:

* 3,4s/PC/computador/
* 2s/\(nunca\).*/\1 falha,
nunca falha,
* ,p
Editor simples
nunca falha,
seja em computador de batata
ou em computador de milhão.
* E
85
* ,p
Editor minimalista
nunca te deixa na mão,
seja em PC de batata
ou em PC de milhão.
* q
$ 

Isso será útil, se você de tempos em tempos salvar as modificações que for fazendo. Dessa forma, poderá sempre retornar ao estado do último salvamento. Mas tenha em mente que usar o comando E para desfazer tudo não permitirá refazer as alterações depois (como ocorre com u para uma única alteração).

Se por um lado, você não pode gradualmente desfazer várias alterações, por outro, o comando u ao menos permite que você compare os resultados antes e depois de aplicar alguma alteração.

Deletando

Agora que você já sabe como desfazer uma ação, é mais seguro falar sobre o comando de deleção de linha, o comando d. Não que você já não pudesse causar algum estrago com os comandos que realizam alterações, como c e s, mas, talvez por questões de hábito, o comando d seja o de maior potencial destrutivo.

Não se assuste. Ele apenas apaga as linhas que você informar. Mas é sempre bom verificar se apagou as linhas certas. Sem endereços, ele apaga a linha atual. Com um endereço, apaga a linha fornecida, e com dois, apaga todo um intervalo de linhas. Preste atenção especialmente quando utilizar pesquisas como endereços para esse comando.

Este comando também pode ser útil para limpar o buffer e recomeçar a edição do zero.

$ ed
* a
Este
texto
será
apagado
.
* ,d
* =
0
* Q

Se prestar atenção, o comando d possui o mesmo efeito de c, informando um ponto logo em seguida para fornecer uma entrada vazia no lugar. Apenas é um pouco mais prático.

Juntando e separando

Juntar e separar linhas é uma atividade corriqueira na edição de texto. Vamos ver como isso pode ser feito no ed. Sobre separar, não há muita novidade, podemos usar o comando s para isso.

$ ed
* a
Essa linha será dividida
.
* s/linha será/linha\
será/
* ,p
Essa linha
será dividida
* 

A única novidade que temos para comentar nessa operação está no fato de termos usado uma barra invertida no termo substituto. Quando fazemos isso, inserimos uma quebra de linha nele, que é o que queremos nesse caso.

Para juntar as linhas de volta, vamos conhecer um novo comando, o j. Por padrão, se não inforado nenhum endereço, ele junta a linha atual com a próxima. Ele não insere espaços entre as linhas agrupadas, portanto é sempre bom inserir esse espaço antes, com o comando s.

* 1s/$/ /
* jp
Essa linha será dividida
* Q
$ 

Novamente, podemos usar o comando p (ou qualquer outro comando de visualização, isto é, n ou l) em conjunto, para mostrar o resultado na sequência.

Quando queremos juntar todas as linhas de um arquivo, podemos usar o comando ,j. Caso seja necessário separá-las com espaços, então o comando ,s/$/ / também deverá ser usado antes.

Comandos globais

Finalmente, vamos falar de um dos recursos mais poderosos do editor ed. Se você já usou awk, talvez esse recurso pareça familiar. Comandos globais servem para, dado um conjunto de linhas que se encaixem em uma expressão regular, aplicar os comandos que você definir (por padrão, apenas mostrar a linha, com p).

Até aqui, vimos a aplicação de comandos a mais de uma linha, usando dois endereços, mas isso obriga que a aplicação seja para uma faixa contínua de linhas. Com comandos globais, podemos aplicar comandos a linhas específicas, que atendem o critério especificado.

Outro programa que torna essa interface familiar é o grep, e não por acaso: o comando grep tem esse nome por causa do ed. Para entender o porquê, vamos analisar a estrutura padrão dos comandos globais:

g/expressão regular/comando(s)

Bem, eu já disse que o comando padrão é p, e expressão regular, em inglês pode ser abreviada como RE, logo teríamos (g/RE/p). Vamos ver um exemplo em ação, novamente com o arquivo area.c.

$ ed area.c
480
* g/raio
                printf("Uso: %s raio\n", argv[0]);
                double raio = atof(argv[1]);
                double area = area_circulo(raio);
*  

Veja o que aconteceu aqui. Eu pesquisei pelo termo “raio” dentro do buffer, e como não especifiquei nenhum comando, a ação padrão foi o comando p, que mostrou as linhas que possuem o termo.

Isso é bastante útil para pesquisar pela ocorrência de variáveis, funções ou macros dentro de um programa, mas igualmente útil para pesquisar por qualquer termo em um arquivo de texto normal. E lembrando, podemos usar expressões regulares mais sofisticadas também para fazer a pesquisa. Além disso, podemos definir mais de um comando para cada ocorrência.

Digamos que eu quisesse, por exemplo, juntar os retornos de função e nome da função na mesma linha. Eu precisaria fazer duas coisas: primeiro, adicionar um espaço depois do retorno, e depois juntar as duas linhas. Podemos então fazer o seguinte:

* g/^[_[:alpha:]]*(/-s/$/ /\
jp
double area_circulo(double r)
int main(int argc, char **argv)

“Mas o quê!?”

Sim, confuso, à primeira vista, não? Vamos desmistificar esse exemplo. Primeiro, definimos um comando global que vai operar sobre uma função. As funções nesse arquivo possuem o nome no início da linha, e são compostos por uma quantidade variável de caracteres alfabéticos e/ou sublinhas (_), seguida de uma abertura de parênteses. Daí temos a expressão regular ^[_[:alpha:]]*(.

Na parte onde definimos os comandos, começamos voltando uma linha (-) e fazendo nela uma substituição no fim da linha, para incluir um espaço (-s/$/ /). Depois, adicionamos uma barra invertida (\), pois é assim que passamos mais de um comando para um comando global. Adicionar uma barra invertida indica que haverá outro comando na sequência, para ser aplicado.

O comando seguinte é jp, que junta as linhas e mostra o resultado em seguida. Já voltamos uma linha no comando anterior, de substituição, portanto não é preciso nem mesmo informar endereços ao comando j, que já vai usar a linha atual e a próxima na junção.

É importante saber que o comando de desfazer (u), quando aplicado sobre alterações de um comando global, reverte todo o comando global. Então digamos que eu não tenha gostado do resultado da operação anterior. Eu poderia usar u para desfazer a alteração em todas as funções (duas linhas, nesse caso). Vejamos na prática:

* u
* g/^[_[:alpha:]]*(/-,.p
double
area_circulo(double r)
int
main(int argc, char **argv)
* 

A aplicação de regras de estilo no código é melhor conduzida por formatadores automáticos (em C, temos por exemplo indent e astyle), e em outra seção discutiremos como podemos tirar proveito disso no ed. Mas note que comandos globais podem ser úteis em vários cenários diferentes. Podemos executar qualquer ação ou conjunto de ações sobre várias partes de um arquivo, e note que as referências de posição são atualizadas para cada linha contemplada.

Nesse último exemplo, veja que usei o intervalo -,. para mostrar a linha atual e a anterior. Para cada linha referida pelo comando global (ou seja, a linha de uma função), eu mostrei a linha anterior também, que possui o tipo. Portanto, as referências de próximo/anterior (ou mesmo próxima ocorrência de uma pesquisa, ou ocorrência anterior) podem ser usadas nesse sentido.

Então, se eu quiser, por exemplo, mostrar o corpo das duas funções, eu poderia fazer o seguinte:

* g/^[_[:alpha:]]*(/-,/^}/p
double
area_circulo(double r)
{
        return 4.0 * M_PI * QUADRADO(r);
}
int
main(int argc, char **argv)
{
        if (argc < 2) {
                printf("Uso: %s raio\n", argv[0]);
                exit(1);
        } else {
                double raio = atof(argv[1]);
                double area = area_circulo(raio);
                printf("%g\n", area);
        }

        return 0;
}
* 

Ou seja, mostrar da linha anterior (retorno da função) até o fechamento do corpo da função (próximo fecha-chaves em início de linha).

Lembra quando eu disse que você pode ver o resultado de várias substituições, ao usar comandos globais? Pois bem, você pode usar um termo abrangente na pesquisa do comando global, e então fazer a substituição mostrando o resultado no final, vide estrutura a seguir:

g/.*/s/expressão/substituição/p

Existem algumas variações do comando global. Uma delas é o comando global interativo, G, que permite que para cada ocorrência encontrada, um comando (ou lista de comandos) diferente seja usado. Podemos, por exemplo, usar um comando global interativo para adicionar um comentário antes de cada função. Como cada comentário depende da função referida, não poderíamos aplicar o mesmo comando para todas as ocorrências tornando o comando g inadequado. Nesse caso, o comando G pode nos ajudar.

* G/^[_[:alpha:]]*(
area_circulo(double r)
-i\
/* Calcula área de um círculo para um dado raio */\

main(int argc, char **argv)
-i\
/* Função principal,\                                              
 * obtém raio como argumento e retorna resultado\
 */\
* w
612
* 

Perceba que nesse caso, para cada linha inserida (com o comando i) foi necessário usar uma barra invertida ao final. Em comandos globais interativos, o modo de entrada se encerra com qualquer fim de linha que não termine com uma barra invertida.

Vejamos o resultado dessa alteração:

* g!/\*!.,/^[_[:alpha:]]*(/p
/* Calcula área de um círculo para um dado raio */

double
area_circulo(double r)
/* Função principal,
 * obtém raio como argumento e retorna resultado
 */

int
main(int argc, char **argv)
* 

Veja que, assim como nos comandos de substituição, comandos globais também aceitam separadores diferentes da barra, para melhorar a legibilidade. Porém o asterisco precisou ser escapado com \, pois caso contrário seria considerado um quantificador da expressão regular.

Assim como em comandos globais comuns, os comandos globais interativos podem ser desfeitos por completo usando o comando u.

Outra variante do comando global é o comando global invertido, v. Ele é quase igual ao comando global comum, porém no lugar de operar sobre linhas que se conformam ao critério de pesquisa, ele opera sobre as linhas que não se conformam. Por exemplo, digamos que eu queira filtrar todos os comandos do programa (isto é, as linhas terminadas em ;) para observar apenas o fluxo geral dele:

* v/;$
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define QUADRADO(x) ((x)*(x))

/* Calcula área de um círculo para um dado raio */

double
area_circulo(double r)
{
}

/* Função principal,
 * obtém raio como argumento e retorna resultado
 */

int
main(int argc, char **argv)
{
        if (argc < 2) {
        } else {
        }

}
* 

E por fim, mais uma variante é o comando global invertido interativo, V, que de modo semelhante ao comando G permite que uma ação diferente seja aplicada para cada ocorrência encontrada (no caso do invertido, não encontrada). Tal como nas variantes anteriores, os comandos globais invertidos podem ser desfeitos de uma só vez com u.

Grandes poderes

Lembra-se quando mencionei o “potencial destrutivo” do comando de deleção, d? Pois bem, ao usá-lo em um comando global, redobre a atenção. Digamos que de repente eu decidi que quero remover os comentários do programa. Vejamos o que pode acontecer:

* g!/\*!.,/\*\//+d
* ,p
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define QUADRADO(x) ((x)*(x))

int
main(int argc, char **argv)
{
        if (argc < 2) {
                printf("Uso: %s raio\n", argv[0]);
                exit(1);
        } else {
                double raio = atof(argv[1]);
                double area = area_circulo(raio);
                printf("%g\n", area);
        }

        return 0;
}
* 

Ops! Por acidente apaguei toda a função area_circulo. Por que isso aconteceu? Vamos olhar mais atentamente para o comando executado: primeiro defino um comando global comum (g), buscando por todo início de comentário (/\*, asterisco escapado), e então da linha atual até a que possui o fechamento do comentário (\*\/, ambos barra e asterisco escapados) mais uma (+), pois há uma linha vazia após cada comentário que também quero remover), apagar sa linhas (d).

O problema é que o primeiro comentário não é multi-linha, tanto a abertura (/*) como o fechamento (*/) estão na mesma linha. Logo, se eu pesquiso pela próxima ocorrência, o editor salta para o fechamento de outro comentário, apagando tudo que há entre eles. Felizmente, podemos reverter esse pequeno desastre (mas que poderia ser um grande) com o comando u.

Como poderíamos fazer essa remoção dos comentários então? Aqui podemos recorrer ao comando global interativo, definindo uma sequência de ações específica para cada comentário:

* G!/\*
/* Calcula área de um círculo para um dado raio */
.,+d
/* Função principal,
.,/\*\//+d
* ,p
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define QUADRADO(x) ((x)*(x))

double
area_circulo(double r)
{
        return 4.0 * M_PI * QUADRADO(r);
}

int
main(int argc, char **argv)
{
        if (argc < 2) {
                printf("Uso: %s raio\n", argv[0]);
                exit(1);
        } else {
                double raio = atof(argv[1]);
                double area = area_circulo(raio);
                printf("%g\n", area);
        }

        return 0;
}
* w
480
* q
$ 

Como pode ver, voltamos ao estado anterior, antes da inclusão dos comentários. Se reparar nas contagens de bytes, elas ajudam a confirmar isso.

Reutilizando pesquisas em comandos globais

Lembra do que falei sobre pesquisas poderem ser reaproveitadas independente do local onde foram usadas? Pois então, isso também vale para os comandos globais. Isso costuma ser útil quando você quer aplicar uma substituição dentro de um comando global, e o critério da substituição é o mesmo da pesquisa feita para filtrar as linhas. Nesse caso você não precisa digitar a pesquisa duas vezes.

A estrutura de um comando assim seria:

g/pesquisa/s//substituto/

Note as duas barras em sequência nesse caso. Como a pesquisa já foi feita no comando global, ela pode ser omitida no comando de substituição. Nesse caso o ed infere que será a mesma usada anteriormente.

Marcadores

Se você tem o hábito de ler (e para ter chegado até aqui, é de se pensar que sim), deve se lembrar dos marcadores de páginas: ao interromper a leitura, você coloca o marcador na página e fecha o livro. Depois, quando retoma a leitura consegue voltar imedatamente ao ponto onde parou, sem precisar ficar procurando a página.

Você poderia simplesmente memorizar a página ou capítulo onde parou? Poderia. Poderia revirar página por página até encontrar o ponto onde parou? Também poderia. Mas nada disso seria tão prático quanto usar um marcador.

No ed, é a mesma coisa. Ao navegar pelo buffer, você pode aplicar marcadores. Depois, pode voltar à linha marcada a qualquer momento. Você poderia memorizar o número da linha, ou a ocorrência de algum texto dentro dela? Poderia. Poderia percorrer todo o buffer desde o início à procura da linha desejada? Poderia. Mas nada disso seria tão prático quanto usar um marcador.

Da mesma forma que nos livros, no ed você pode a qualquer momento mover o marcador de um ponto a outro, isto é de uma linha para outra. A diferença é que no ed você possui 26 marcadores à disposição, e pode marcar linhas diferentes (ou iguais) com cada um deles. Então geralmente nem precisa se preocupar em reaproveitar os marcadores.

“Mas por que 26?”, você talvez pergunte. Simples, porque é um para cada letra de “a” a “z”. Assim fica mais fácil memorizar os marcadores: pela letra.

Para aplicar marcadores, usamos o comando k. Esse comando usa um endereço (que por padrão será a linha atual), e também um parâmetro (que será a letra do marcador). Depois, para usar aquele marcador como endereço, você usa um apóstrofo (') seguido da letra do marcador. Apontar para um marcador não aplicado é uma operação inválida, que emitirá um erro.

Vejamos um exemplo de uso:

$ ed area.c
480
* ?main?km
* n
26      }
* 'm
main(int argc, char **argv)
* 

Perceba o que aconteceu aqui: ao iniciar, estamos no fim do arquivo (linha 26). Então uso uma pesquisa em sentido reverso, para buscar pelo termo main, e então aplicar o marcador m. Note que após essa operação a posição dentro do arquivo não é alterada. Em seguida, uso o endereço 'm para navegar para a linha marcada.

A ordem aqui foi apresentada desse modo para fins didáticos, isto é, para mostrar que o comando k não altera a posição, mas você pode, se preferir, navegar para a linha primeiro, e então aplicar o marcador sobre ela (como falei, se não informado o endereço, o marcador é aplicado sobre a linha atual).

Podemos adotar vários padrões de uso dos marcadores. Por exemplo, podemos marcar um ponto A e um ponto B para usar como endereços:

* /^}
}
* kn
* 'm-,'nn
13      int
14      main(int argc, char **argv)
15      {
16              if (argc < 2) {
17                      printf("Uso: %s raio\n", argv[0]);
18                      exit(1);
19              } else {
20                      double raio = atof(argv[1]);
21                      double area = area_circulo(raio);
22                      printf("%g\n", area);
23              }
24
25              return 0;
26      }
* 

Nesse caso, usei os marcadores m e n como pontos A e B (talvez você prefira usar os marcadores a e b, isso é mais uma convenção que fica a seu critério). Às vezes, você não precisa nem mesmo definir o ponto B, pode usar a posição atual como um dos pontos:

* /main/-
int
* /^}/km
* ;'mn
13      int
14      main(int argc, char **argv)
15      {
16              if (argc < 2) {
17                      printf("Uso: %s raio\n", argv[0]);
18                      exit(1);
19              } else {
20                      double raio = atof(argv[1]);
21                      double area = area_circulo(raio);
22                      printf("%g\n", area);
23              }
24
25              return 0;
26      }
* q
$ 

Lembrando, quando usamos o separador de ponto-e-vírgula (;), se omitido o primeiro endereço já se assume o endereço atual. Então nesse caso, eu fui para o início da função, marquei o fim e mostrei o intervalo do ponto atual até o marcador m (nesse caso apontando para o final da função).

Outro padrão de uso que pode ser interessante em algumas ocasiões é usar comandos globais interativos para passar por vários locais de um arquivo, e para cada um aplicar um marcador diferente.

Podemos estabelecer vários tipos de convenção para os marcadores. Por exemplo, marcar pontos de interesse em um arquivo, escolhendo as letras por ordem alfabética, ou usando uma letra que tenha ocorrência na linha referida, por exemplo. E podemos também, como feito nos exemplos anteriores, definir funções específicas para cada marcador (m e n para marcar um intervalo, por exemplo).

Outras funções possíveis (e sugestões de marcador) seriam:

  • o último local importante que você visitou (z);
  • uma linha que você poderá precisar copiar várias vezes (c);
  • uma linha que você sabe que vai precisar modificar depois (d).

São apenas sugestões. Você pode definir suas próprias convenções. Agora, um detalhe que precisa considerar é que linhas que forem modificadas perdem o seu marcador. Evite marcar linhas que vai modificar a não ser que, como sugeri acima, você já tenha o pressuposto de que esse marcador pode não durar muito tempo.

Caso queira remover um marcador, basta substituir o conteúdo de uma linha pelo mesmo conteúdo. Isso já conta como uma alteração, e faz com que o marcador seja removido.

* s/.*/&

Caso queira remover todos os marcadores no buffer atual, você pode simplesmente recarregar o arquivo (se ele já estiver salvo). Caso haja mudanças ainda não salvas e você não queira salvar o conteúdo do buffer, você pode usar um comando global substituindo cada linha por ela mesma.

* g/./.s/.*/&

Para arquivos muito grandes isso não é recomendado por não ser um método muito eficiente, e nesse caso pode ser mais interessante salvar o arquivo, ou se isso não for possível, navegar para cada um dos marcadores e aplicar a substituição na linha. Pode não ser muito confortável, mas será mais eficiente que aplicar a substituição linha por linha.

De qualquer modo, é improvável cair em alguma situação na qual precise remover todos os marcadores.

Também mencionei a cópia de linhas, uma operação relativamente comum na edição de texto, mas que ainda não vimos como pode ser feito no ed. Vamos passar para esse tópico.

Copiando e movendo linhas

Este é um assunto que pode dar um nó na sua cabeça, dependendo do quanto já tiver se acostumado com outros editores (suponho que muito). A questão é que geralmente os editores de texto trabalham do seguinte modo: ao copiar um fragmento de texto (que nem precisa ser uma linha inteira), ele é armazenado em um local temporário, e poderá depois ser “colado” em outros lugares. Se “recortado”, então ele é também copiado para um local temporário, e em seguida removido do seu local original (e pode ser colado várias vezes).

No ed isso funciona de outro jeito. Temos dois comandos para aprender aqui, o comando m (para mover linhas), e o comando t para transferir (o que no jargão do ed equivale a copiar) linhas. Esses comandos trabalham com uma ou mais linhas inteiras, nunca partes delas. E não há cópia para um local temporário, é um comando que você determina a origem (uma ou mais linhas) e o destino (para onde mover ou copiar).

Então a estrutura padrão desses dois comandos é composta por zero, um ou dois endereços, o comando propriamente, e depois o endereço de destino. Algumas implementações podem assumir um default para o destino, mas isso não é um comportamento padrão. As regras de endereçamento (zero, um ou dois) nesse caso funcionam do mesmo modo que já vimos em outros comandos.

A linha (ou as linhas) de origem será copiada ou movida para o local imediatamente depois do endereço de destino fornecido.

Duplicar uma linha, portanto, é uma operação super simples:

$ ed
* a
teste
.
* t.
* ,p
teste
teste
* Q
$ 

No lugar de t. eu poderia ter usado t-, com o mesmo efeito. Em vez de copiar a linha para depois, copiaria para antes. Como as linhas são idênticas, não faria nenhuma diferença prática.

Conforme expliquei, não há noção de cópia do conteúdo para uma área de transferência (um local temporário) para colar depois. Isso pode causar algum desconforto. Uma operação análoga que pode facilitar a sua vida é usar um marcador sobre a linha que quer copiar (ou mover), e depois usar esse marcador como endereço. Vejamos um exemplo simplório:

$ ed poema_ed.txt
85
* 2
nunca te deixa na mão,
* kc
* $
ou em PC de milhão.
* 'ct.
* 'ct.
* 'ct.
* ,p
Editor minimalista
nunca te deixa na mão,
seja em PC de batata
ou em PC de milhão.
nunca te deixa na mão,
nunca te deixa na mão,
nunca te deixa na mão,
* Q
$ 

Ou seja, no lugar de copiar o texto para uma área de transferência, copiamos uma referência para a linha a ser copiada, o que tem efeito bem próximo.

Como também falei antes, não é possível copiar um fragmento de uma linha, apenas a linha inteira. Para ter o efeito de uma cópia parcial, você precisará de dois comandos em sequência, um para copiar a linha, e outro, de substituição (relembrando, o comando s), para remover as partes desnecessárias da linha.

Se for um fragmento que você vai copiar várias vezes, isso pode ser pouco prático, então o que pode facilitar sua vida é duplicar a linha que contém o fragmento, alterar a duplicata para ficar como quer, marcar essa linha (como sugeri antes, pode usar um marcador padronizado para isso), e então usar o marcador como origem todas as vezes. Ao fim, você apaga a linha marcada.

Pelo bem da didática, vamos fazer mais um exercício com o arquivo area.c para demonstrar esse tipo de operação. Podemos adicionar um comentário antes da macro QUADRADO, depois copiamos esse comentário substituindo o nome QUADRADO por reticências que editaremos depois. Então copiamos esse comentário modificado para usar antes das funções, e ao fim apagamos a linha modificada.

$ ed area.c
* /QUADRADO
#define QUADRADO(x) ((x)*(x))
* i
/* Descrição de QUADRADO */
.
* t.
* s/QUADRADO/.../
* -,+p
/* Descrição de QUADRADO */
/* Descrição de ... */
#define QUADRADO(x) ((x)*(x))
* -kc
* /area_circulo/-2

* 'ct.
* ;+2p
/* Descrição de ... */
double
area_circulo(double r)
* /main/-2

* 'ct.
* ;+2p
/* Descrição de ... */
int
main(int argc, char **argv)
* 'cd
* 

Explicando o passo a passo desse exemplo: primeiro navegamos para a linha da macro QUADRADO. Imediatamente antes inserimos um comentário (o comentário é inútil, mas volto a dizer, esse exemlo é só pela didática). O comentário em questão serve só para essa macro, e não para as duas funções (area_circulo e main). Então o que fazemos: duplicamos a linha, e substituímos o nome da macro por reticências, para deixar evidente que será ajustado ainda.

Para ver os resultados, usei -,+p, mostrando o intervalo de uma linha antes para uma depois da duplicata alterada (isto é, a que possui reticências). Lembrando, o comando p altera a posição para a última linha exibida, então o comando seguinte é -kc para marcar a linha anterior (novamente, a duplicata). Por enquanto mantemos essa linha aí, para poder fazer as cópias.

Então vamos para /area_circulo/-2, isto é, a linha que precede o retorno da função (double). Nesse ponto colocaremos uma das cópias, então usamos 'ct. (transferir, ou seja, copiar a linha marcada com c e colar abaixo). Com ;+2p conferimos o resultado.

Fazemos o mesmo para main em seguida. Por fim, apagamos a linha marcada com 'cd. Ao fim, você deverá observar o seguinte resultado:

* ,p
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

/* Descrição de QUADRADO */
#define QUADRADO(x) ((x)*(x))

/* Descrição de ... */
double
area_circulo(double r)
{
        return 4.0 * M_PI * QUADRADO(r);
}

/* Descrição de ... */
int
main(int argc, char **argv)
{
        if (argc < 2) {
                printf("Uso: %s raio\n", argv[0]);
                exit(1);
        } else {
                double raio = atof(argv[1]);
                double area = area_circulo(raio);
                printf("%g\n", area);
        }

        return 0;
}
* 

Parece confuso? Com o tempo isso vira coisa rotineira. Mas agora vou mostrar uma outra forma de fazer isso, que poucos editores oferecem algo parecido: aplicar várias cópias de uma só vez, em várias partes do documento com base em algum padrão. Podemos fazer isso usando um comando global.

Primeiro, vamos limpar todas as alterações que fizemos, e em seguida, usamos um comando global para pesquisar por cada função e inserir uma cópia do comentário. Nesse caso específico, não precisaremos criar a duplicata, podemos já copiar e alterar no próprio comando global:

* E
480
* /QUADRADO
#define QUADRADO(x) ((x)*(x))
* i
/* Descrição de QUADRADO */
.
* g/^[_[:alpha:]]*(/'ct-2\
s/QUADRADO/.../
* Q
$ 

Explicando: no comando global (g) usamos uma expressão para buscar por funções (nesse caso representadas por ^[_[:alpha:]]*(), e então transferimos (t) a linha marcada com c ('c) para duas linhas antes do nome da função (-2). Então usamos uma barra invertida para indicar que outro comando será fornecido, que será a substituição do termo QUADRADO pelas reticências.

O resultado final será o mesmo de antes. Para apenas duas cópias pode não significar muito, mas imagine se tivesse que fazer essa operação para algumas dezenas de linhas. E podemos ir até um pouco além, e no lugar de um comando global, usar um comando global interativo (G). Assim, para cada função já podemos usar o nome da função no lugar das reticências. Isto deixarei como exercício para você praticar.

Falei pouco sobre o comando m mas basicamente é uma lógica bem parecida com o t, a diferença básica é que ele move a linha (ou as linhas) em vez de copiar. Um ponto que vale destacar é que para copiar ou mover linhas para o início do arquivo, o destino deverá ser 0 (isto é, antes da primeira linha).

E lembrando: marcadores podem ser um grande auxílio para lidar com esses comandos. Se por um lado não podemos copiar o texto para uma área temporária (para colar depois), podemos salvar o trecho a ser copiado ou movido com marcadores. Para um intervalo com mais de uma linha, podemos usar dois marcadores, definindo início e fim do intervalo.

Reaproveitando arquivos

Até aqui vimos algumas formas de fornecer conteúdo para o buffer, como carregar um arquivo, adicionar ou inserir conteúdo, transferir (copiar) linhas, etc. Agora veremos mais uma forma: a reutilização de outros arquivos, com o comando r.

Esse comando tem alguma semelhança com o comando e, no sentido de que ele carrega o conteúdo de um arquivo no buffer, porém a diferença é que ele não limpa o buffer antes. Em vez disso, ele carrega o conteúdo do arquivo imediatamente após o endereço especificado (caso não seja especificado, por padrão ele insere ao fim do arquivo).

Vejamos um exemplo:

$ ed
* a
Vamos ler um poema:

.
* f
?
* h
no current filename
* r poema_ed.txt
85
* ,p
Vamos ler um poema:

Editor minimalista
nunca te deixa na mão,
seja em PC de batata
ou em PC de milhão.
* f
poema_ed.txt
* Q
$ 

Aqui temos dois pontos importantes para destacar: o primeiro é que esse comando também emite uma mensagem informando quantos bytes foram lidos (neste caso, do arquivo poema_ed.txt).

O segundo ponto é que, como pode notar, antes da leitura o nome de arquivo não está configurado (já que iniciamos o editor sem informar nenhum arquivo para ser aberto no buffer), mas depois de ler o arquivo poema_ed.txt, o nome de arquivo passa a ser o do arquivo lido. Isso pode ser o que você quer, ou não. É importante considerar esse fato porque depois dessa leitura, se você salvar o conteúdo do buffer ele será escrito sobre o arquivo poema_ed.txt (a menos que antes você troque o nome do arquivo, para salvar em outro lugar).

Por outro lado, se você abrir um arquivo existente, ler um arquivo com o comando r não vai fazer nenhuma alteração no nome de arquivo. Isto é útil, por exemplo, quando você quer restaurar um backup. Suponha que você gerou um backup do arquivo area.c, chamado area.c.bkp. Você fez edições no original (area.c) e de repente percebe que está tudo errado. Então resolve restaurar do backup. Vejamos como isso pode ser feito:

$ ed area.c
586
* f
area.c
* ,d
* r area.c.bkp
480
* f
area.c
* w
480
* q
$ 

Perceba que nesse caso, mesmo depois de ler o arquivo area.c.bkp o nome de arquivo configurado permaneceu o mesmo (area.c), pois já havia sido configurado antes da leitura.

O comando r também pode ser útil para trabalhar com modelos de arquivos, isto é, você pode carregar um arquivo-modelo para dentro do buffer, fazer as edições necessárias e salvar como um novo arquivo. Claro, você poderia fazer o mesmo carregando um arquivo com e e depois trocando o nome do arquivo, porém se precisar usar mais de um modelo dentro de um mesmo arquivo, a opção pelo comando r fará mais sentido.

Como expliquei, o comando r pode ser usado para carregar o conteúdo de outro arquivo em qualquer parte do buffer. Embora o padrão seja no final, você pode especificar um endereço antes do comando. Se usar o endereço zero (0), então o conteúdo será carregado no início do arquivo.

Interagindo com o sistema

Eu havia comentado brevemente sobre a integração do ed com os comandos do sistema. Vamos detalhar como isso funciona. Sendo o editor padrão do UNIX, seria até estranho que ele não tirasse vantagem dos demais comandos disponíveis no sistema. É parte da filosofia UNIX, afinal. É nessa integração que reside o maior potencial do editor, pois qualquer comando disponível no sistema pode ser utilizado dentro do ed.

Para executar comandos do sistema no ed, usamos o comando !. Por exemplo:

$ ed
* !ls
area.c        poema_ed.txt
!
* 

Aqui temos duas observações a fazer: a primeira é que ao terminar a execução do comando, o ed emite uma linha com um ponto de exclamação. Isto seve para você saber que a execução do comando terminou naquele ponto. A segunda observação a ser feita é que, como mostra esse exemplo (usando ls), podemos listar os arquivos presentes no diretório atual, facilitando assim para saber que arquivos poderiam ser carregados no ed.

Há vários outros usos interessantes que podemos fazer desse recurso. Quer detalhes sobre data e hora? Use cal e date. Limpar a tela? Use clear. Quer ver o conteúdo de outro arquivo sem modificar o buffer? Use cat. Ou, se preferir, pode usar more ou less (sim, não há problema nenhum acionar um comando que usa interface ncurses!). Quer abrir um novo shell, sem ter que fechar o editor e perder o que estava fazendo? Use sh (ou o shell que utilizar no seu sistema).

Você pode até mesmo abrir outro editor ed dentro do editor, com um buffer novo, sem qualquer interferência entre os dois. Mas eu recomendo ter bastante cautela ao fazer isso. Pode ser fácil se confundir sobre qual arquivo está editando, especialmente se aninhar os comandos mais de uma vez.

E você não precisa se restringir apenas aos comandos padrão do sistema. Qualquer comando disponível e que funcionaria normalmente no shell pode ser usado dessa forma.

Não se iluda!

Usar o comando cd no lançador de comandos do ed não surtirá o efeito que você possivelmente espera. Os comandos são executados em um contexto isolado, então a troca de diretórios ocorreria em uma nova sessão, que encerraria logo em seguida, e para todos os efeitos você continua no mesmo diretório de antes.

Se duvida, compare a saída de pwd antes e depois de executar cd e verá que não houve alteração alguma. Em outras palavras, não há persistência de contexto entre a execução dos comandos do sistema no ed.

Se quiser repetir o último comando utilizado, basta usar o atalho !! (duas exclamações).

* !!
ls
area.c        poema_ed.txt
!
* 

Nesse caso, como houve uma substituição no comando (isto é, do comando fornecido, !! pelo comando efetivo, ls), ele é exibido logo antes de ser executado, para que você possa ter certeza de qual foi o comando executado.

Podemos também usar o sinal de percentual (%) em comandos como referência ao nome de arquivo memorizado. Isso pode ser útil, por exemplo quando estamos editando um arquivo-fonte e queremos então acionar um compilador ou interpretador para testá-lo logo em seguida. Nesse caso, é necessário salvar o conteúdo do buffer antes de acionar o comando.

$ ed area.c
480
* !cc -Wall -o area %
cc -Wall -o area area.c
!
* !./area 3
113.097
!
* q
$ 

Veja que nesse caso sinal de percentual foi substituído por area.c, e tal como no caso anterior, o comando é exibido logo antes da execução, por ter havido uma substituição, nesse caso, do nome de arquivo. Esse é um programa simples, então apenas compilei e executei. Mas poderia, em um programa mais complexo, acionar o comando make ou qualquer que fosse o comando de construção do programa.

Igualmente, poderia estar manipulando o código de um programa interpretado, e então acionar o interpretador para testá-lo. E para além disso, poderia estar manipulando outros tipos de arquivos, como logs, por exemplo, e usando um comando externo como awk ou sed para interpretar o arquivo segundo alguma regra definida.

Até aqui só vimos uma integração básica. Mas podemos fazer mais: podemos redirecionar entradas e saídas de/para comandos do sistema.

Capturando saídas

Vimos há pouco sobre o comando r e como ele pode ser usado para reaproveitar o conteúdo de outros arquivos (ou do mesmo arquivo que está carregado no buffer). Ocorre que também podemos usar esse comando para reaproveitar a saída de comandos do sistema. Para isso combinamos o comando r com o comando !.

Vejamos um exemplo:

* a
Lista de arquivos locais:

.
* r !ls
20
* f
?
* h
no current filename
* ,p
Lista de arquivos locais:

area.c
poema_ed.txt
* Q
$ 

Perceba que nesse caso, o nome de arquivo não é configurado. Isso não faria sentido, pois a leitura está sendo feita de um comando. No entanto, a contagem de bytes lidos é apresentada.

Podemos usar esse recurso de leitura da saída dos comandos para obter dados de várias fontes diferentes. Podemos obter relatórios do próprio sistema (por exemplo, com o comando top obter uma lista dos top N processos do sistema segundo algum critério, como consumo de memória ou processamento), relatórios sobre o consumo de discos (com df), dentre outros.

Podemos também obter os resultados de consultas a um banco de dados usando clientes de linha de comando, como o sqlite3 para arquivos do SQLite, ou psql para consultas a uma instância de PostgreSQL. Podemos usar navegadores Web de linha de comando como links ou w3m para obter o texto de uma página Web, ou listas de arquivos disponíveis em um servidor remoto, consultando via cliente ftp. Isso só para citar algumas das possibilidades.

Portanto, agora você está livre da tarefa chata de copiar a saída de um comando e gravar em algum arquivo. O ed já te habilita a fazer isso de modo automatizado.

As regras de endereçamento nesse caso são as mesmas da leitura a partir de outros arquivos, isto é, por padrão o conteúdo é gravado no fim do buffer. Caso você esteja no meio do buffer e queira gravar a saída do comando onde está, deve ser informado o endereço atual (.) antes do comando r. E você pode fornecer qualquer outro endereço (inclusive 0, para gravar no início).

Carregando saídas

De modo semelhante à leitura de saídas, podemos também carregar a saída de um comando no buffer, como se estivéssemos carregando um arquivo. Para isso usamos o comando e combinado ao comando !. A diferença é basicamente o fato de que carregar (em contraposição à leitura) limpa o buffer antes de obter a saída do comando, e nesse caso a saída é gravada logo no início do buffer. Portanto, não há endereçamento nesse caso.

$ ed
* e !ls
20
* ,p
area.c
poema_ed.txt
* f
?
* h
no current filename
* Q
$ 

Como no caso da leitura, ao carregar uma saída o nome de arquivo não é alterado. Se um nome de arquivo estava indefinido até então, ele permanecerá indefinido. Se já existia algum, ele é mantido.

Este recurso de carregamento via comandos pode ser bem útil para formatação de arquivos. No ed, quando queremos passar o conteúdo de um arquivo para um comando e substituir esse conteúdo pela saída do comando, precisamos executar duas etapas: primeiro enviar o conteúdo para um comando e salvar em um arquivo temporário. Depois, apagar esse conteúdo do buffer e então carregar a partir do arquivo temporário.

Mas usando o carregamento a partir de um comando, podemos fazer tudo isso em uma única etapa. Imagine o seguinte cenário, temos um arquivo de texto com linhas longas, e queremos formatá-lo, garantindo que as linhas não passem de 72 caracteres. O comando externo fmt pode nos ajudar nisso. Considere o seguinte arquivo de exemplo:

formatado.txt
Este arquivo possui linhas muito longas que vamos reajustar para ficar formatado. Assim
posso redigir no ed sem me preocupar com isso, e formato tudo depois.

Como o comando fmt se encarrega da formatação, só preciso separar os parágrafos, e ele cuida
do resto.

Agora vamos abrir esse arquivo no ed, e realizar a formatação:

$ ed formatado.txt
266
* f
formatado.txt
* e !fmt %
fmt formatado.txt
266
* ,p
Este arquivo possui linhas muito longas que vamos reajustar para
ficar formatado. Assim posso redigir no ed sem me preocupar com
isso, e formato tudo depois.

Como o comando fmt se encarrega da formatação, só preciso separar
os parágrafos, e ele cuida do resto.
* f
formatado.txt
* w
266
* q
$ 

Note que o carregamento nesse caso não alterou o nome de arquivo que já estava configurado (embora seja boa prática conferir o nome salvo, quando estiver em dúvida). O comando e !fmt % carrega a partir da saída do comando externo fmt, que recebe como argumento o nome do arquivo atual (expresso como %). Isso faz com que toda a operação ocorra de uma só vez.

Apenas certifique-se de que o conteúdo do buffer já foi salvo no arquivo (o ed vai emitir um alerta, de qualquer modo, caso haja alterações não salvas).

O mesmo poderia ser feito para formatar códigos C usando ferramentas como indent ou astyle, por exemplo. A limitação dessa abordagem é apenas que só pode ser feito para o arquivo todo, ou seja, não é possível aplicar esse filtro para apenas uma parte do conteúdo do buffer.

Fornecendo entradas

Do mesmo modo que podemos capturar saídas de comandos, podemos também fornecer o texto do buffer como entrada para algum comando. De modo análogo ao que fizemos na leitura, para escrita combinamos os comandos w e !.

Por exemplo, digamos que eu queira ver o texto do poema_ed.txt invertido:

$ ed poema_ed.txt                                                                    
85
* w !rev
atsilaminim rotidE
,oãm an axied et acnun
atatab ed CP me ajes
.oãhlim ed CP me uo
85
* 

Assim como em uma escrita normal, em uma escrita para comandos também é emitida uma mensagem informando quantos bytes foram escritos (nesse caso, enviados para o comando chamado).

Nesse caso, usamos o comando rev para transformar as linhas do buffer. Aqui podemos usar o endereçamento para filtrar uma linha (ou grupo de linhas) que deverão ser enviadas para o comando. Se eu quisesse ver essa reversão apenas para as duas primeiras linhas, eu poderia fazer o seguinte:

* 1,2w !rev
atsilaminim rotidE
,oãm an axied et acnun
43
*

Vejamos um exemplo um pouco mais prático. Digamos que eu queira salvar o conteúdo do buffer em outro arquivo, e quero que seja cifrado, de modo que apenas com o fornecimento de uma senha ele possa ser lido novamente. Nesse exemplo vamos usar o openssl mas ficaria a seu critério qual método utilizar, desde que seja possível obter um texto como entrada e salvar a saída em outro arquivo.

* w !openssl enc -aes-256-cbc -base64 | tee cifrado.b64
enter aes-256-cbc encryption password:
Verifying - enter aes-256-cbc encryption password:
U2FsdGVkX1/oahuQ6qbN2CJbrItETyZdFiqAnWbgoCvqwdDTYlx7Q4QAqLgnkDJ1
U+bNecn64yPdc5oczHqMqcOGTYzTmld8/50/3pJKXvJygYlKiaWslfINypWK/R7R
qW6JkMFPjCh+c6n9EY8aTQ==
85
* q
$ 

Sem entrar em detalhes sobre o comando openssl e seus parâmetros (para isso, você pode consultar o manual dele), o que fazemos aqui é obter o conteúdo do buffer (isto é, o conteúdo carregado do poema_ed.txt), e ciframos com o algoritmo AES, depois convertemos a saída para o formato Base64 e por fim o redirecionamos para um arquivo chamado cifrado.b64.

Aqui chamo atenção para o fato de que podemos usar pipes (|) para redirecionar a saída do primeiro comando para outro (no caso tee, que por sua vez mostra o resultado na tela ao mesmo tempo em que salva o conteúdo no arquivo indicado).

Uma senha é solicitada para o processo de criptografia, em seguida a confirmação da senha e pronto: temos na tela (e também no arquivo cifrado.b64 o resultado desse processo. Feito isso encerramos o editor ed. Como podemos depois, dentro do ed recuperar esse conteúdo? Simples, com o comando r do ed, lendo a saída do openssl:

$ ed
* r !openssl enc -d -aes-256-cbc -base64 -in cifrado.b64
enter aes-256-cbc decryption password:
85
* ,p
Editor minimalista
nunca te deixa na mão,
seja em PC de batata
ou em PC de milhão.
* Q
$ 

Aqui usamos o parâmetro -d para indicar o processo reverso, isto é, decifrar o conteúdo a partir do que foi lido em cifrado.b64. A senha é solicitada, e logo vemos a contagem de bytes lidos desse processo. Ao fim conferimos o conteúdo do buffer que é exatamente o mesmo inicialmente gravado.

Outra situação em que isso pode ser conveniente é quando está editando arquivos-fonte de algum interpretador (sh ou perl, por exemplo). Isso permite que você teste a execução do script que está carregado no buffer (seja ele salvo ou não).

$ ed
* a
echo 'ok'
.
* w !sh
ok
10
* Q
$ 
Vale destacar que esse tipo de interação com o shell não fica associado a um terminal de comandos (TTY), e portanto alguns scripts podem não funcionar dessa forma (por exemplo, um comando que lê alguma entrada via teclado). Nesse caso será mais recomendado usar o comando ! chamando o interpretador diretamente e passando o arquivo como argumento, o que requer salvar o conteúdo do buffer previamente.

Ainda outra situação onde isso pode ser conveniente é quando está lendo algum arquivo e dentro dele há algum trecho estruturado (diagmos, delimitado por tabulações), e queira filtrar colunas dele na visualização. Para exemplificar, vamos considerar o seguinte arquivo de log (campos separados por tabulações):

servidor.log
20:42   ERRO    Arquivo 'naoexiste.txt' não foi encontrado.
20:42   INFO    Gerando novo arquivo de configuração.
20:43   INFO    Arquivo 'servidor.conf' criado.
20:55   ERRO    Usuário 'fulanodetal' não existe.

Agora vamos chamar o comando externo awk para filtrar a saída, capturando apenas linhas de erro, e apenas a mensagem do erro.

$ ed servidor.log
197
* w !awk -F '\t' '/ERRO/ {print $3}'
Arquivo 'naoexiste.txt' não foi encontrado.
Usuário 'fulanodetal' não existe.
197
* q
$ 

Isso está longe de ser uma lista exaustiva de casos em que você pode se beneficiar das interações com o sistema. Aqui a sua criatividade é o que permitirá extrair o máximo de valor desse recurso.

Saindo do padrão

As funcionalidades do ed que mencionei até aqui são todas parte do padrão, e qualquer implementação aderente ao POSIX possui essas características. Porém, algumas funcionalidades são bastante comuns entre diferentes implementações e apesar de não serem parte do padrão (e portanto não garantido que existirá em uma implementação do ed), podem ser bem convenientes.

Deixei essa parte propositalmente para o final, para que em um primeiro momento você pudesse focar naquilo que está garantido pelo padrão.

Se está fora do padrão, isso quer dizer que você não deve usar? Obviamente que não. Se fosse assim, não seriam implementadas. Mas é importante saber como você poderia se virar sem essas opções, portanto para cada uma das funcionalidades a seguir vou explorar também sobre como você pode fazer as mesmas coisas usando apenas o que está no padrão.

Os tópicos a seguir detalham algumas funcionalidades presentes em implementações mais comuns. Além dessas, você pode ainda encontrar outras funcionalidades mais específicas (para estas, será necessáiro consultar a documentação da implementação que estiver usando).

Paginação

Talvez você tenha notado que o ed é muito mais prático para a edição do que na visualização do texto. Isso é herança da filosofia presente no UNIX, na qual há uma separação de funções. O ed é um editor, não visualizador/paginador de arquivos.

Embora existam três comandos diferentes para visualização e você ainda possa usar comandos externos para ler o texto, talvez tenha sentido falta de algo que dê mais conforto para a leitura de um longo texto do início ao fim, um texto que não cabe na tela por inteiro.

Para isto, podemos usar o comando z. Esse comando pode ser entendido como “mostre a linha atual e as N linhas seguintes”, onde N será um valor especificado (como sufixo do comando), ou se não informado, será o máximo que couber na tela junto com a linha atual (tamanho da tela do terminal, em número de linhas, menos 1). Se informado algum valor N, ele passa a ser o tamanho de paginação usado em chamadas subsequentes ao comando z.

Se houver menos linhas do que o tamanho de tela (ou do valor definido), então todas as próximas linhas são exibidas. Se um endereço for usado como prefixo, então aquele endereço será o ponto de partida (útil para incluir a primeira linha).

$ ed poema_ed.txt
85
* 1z
Editor minimalista
nunca te deixa na mão,
seja em PC de batata
ou em PC de milhão.
* e area.c
480
* 1z3
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

* z
#define QUADRADO(x) ((x)*(x))

double
area_circulo(double r)
* z
{
        return 4.0 * M_PI * QUADRADO(r);
}

* 

Note que após a primeira paginação, apenas usar o comando z faz com que a leitura do arquivo prossiga do ponto onde havia parado.

Se utilizado um sufixo para um dos comandos de visualização (p, n ou l), então o paginador vai exibir as linhas segundo a formatação desses comandos. Informar p como sufixo é redundante, pois esse já é o padrão.

* z3n
13      int
14      main(int argc, char **argv)
15      {
16              if (argc < 2) {
* 

Alternativas dentro do padrão

Você pode usar paginadores externos para ler o arquivo.

* !more %
more area.c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define QUADRADO(x) ((x)*(x))

double
area_circulo(double r)
{
        return 4.0 * M_PI * QUADRADO(r);
}

int
main(int argc, char **argv)
{
        if (argc < 2) {
                printf("Uso: %s raio\n", argv[0]);
                exit(1);
        } else {
                double raio = atof(argv[1]);
                double area = area_circulo(raio);
                printf("%g\n", area);
        }

        return 0;
}
!
* .=
16
* 

Perceba que ao usar um paginador externo (ou qualquer outro comando externo para exibir o arquivo), a posição dentro do buffer não é alterada. Porém é importante destacar que usar um paginador externo desse modo requer que o conteúdo do buffer tenha sido salvo. Caso contrário, estará lendo um conteúdo diferente do que está no buffer.

Talvez você tenha pensado em usar um redirecionamento do buffer para um comando externo, pois assim pode paginar o conteúdo sem antes precisar salvar o buffer. Mas isso não é recomendado, pois como já foi alertado, esse tipo de interação não fica vinculada a um terminal. Dependendo da quantidade de linhas, o paginador pode encerrar o editor ed, antes que você tenha a oportunidade de salvar seu conteúdo.

Outra opção de paginação é, no lugar de um comando externo, usar o próprio comando p (ou qualquer outro comando de visualização) com o delimitador de ponto-e-vírgula.

* 4

* ;p

#define QUADRADO(x) ((x)*(x))

double
area_circulo(double r)
{
        return 4.0 * M_PI * QUADRADO(r);
}

int
main(int argc, char **argv)
{
        if (argc < 2) {
                printf("Uso: %s raio\n", argv[0]);
                exit(1);
        } else {
                double raio = atof(argv[1]);
                double area = area_circulo(raio);
                printf("%g\n", area);
        }

        return 0;
}
* q
$ 

Nesse caso, omiti o endereço final, mas reitero: se não informado, nesse caso, o endereço final será o final do buffer, não o quanto cabe na tela. Você pode usar um valor relativo (como +25, por exemplo) no segundo endereço, o que fará com que avance apenas esse número de linhas.

Essa opção é vantajosa, inclusive, caso queira usar os comandos n e l para mostrar as linhas e/ou representação inequívoca, o que não seria necessariamente possível em um paginador externo.

Repetir última substituição

Se você quiser aplicar uma mesma subsituição que já fez em outras linhas (ou até na mesma linha), isso é possível usando o comando s sem parâmetros.

$ ed
* a
abra
kadabra
.
* 1s/abra/obra
obra
* +s
kadobra
* Q
$ 

Especialmente quando a substituição envolver um critério de pesquisa e/ou um termo substituto muito grandes, esse tipo de abreviação pode ser bastante conveniente.

Alternativas dentro do padrão

Bem, aqui vou chover no molhado, pois já vimos como pode ser feito no padrão: ao omitir o critério de pesquisa, repetimos a mesma pesquisa (o que pode ser feito dentro de um comando de substituição), e o símbolo % isolado no termo substituto significa repetir o último termo substituto, logo temos:

s//%/

O que tem um efeito similar ao comando abreviado s. A única diferença é que esse comando não mostra o resultado logo em seguida, o que pode ser resolvido anexando o comando p ao final:

s//%/p

Salvar e sair

Esse é um recurso bem simplório. Você pode juntar os comandos w e q para salvar e sair em sequência, no lugar de salvar primeiro e sair depois. Pelo padrão POSIX você precisaria executar os comandos separadamente, mas a maior parte das implementações permite essa combinação em particular.

Alternativas dentro do padrão

Não há muito a dizer aqui. Basta usar os comandos separadamente.

Anexar

Uma variação do comando de escrita (w) é o de anexação (W), que serve para escrever as linhas endereçadas ao final de algum arquivo especificado (por padrão, o arquivo memorizado, que podemos verificar ou alterar com o comando f). Assim como o comando de escrita, no comando de anexação, se não for informado nenhum endereço, assume-se que todo o conteúdo do buffer deve ser utilizado.

Anexar, no lugar de escrever, significa que o arquivo de destino não será sobrescrito, e o conteúdo que ele já possui não será perdido. Apenas será concatenado com as linhas fornecidas nesse comando.

Algumas situações em que isso pode ser útil são aquelas nas quais você varre um determinado arquivo a procura de linhas com alguma relevância para a análise que estiver fazendo, e então direcionar essas linhas para algum outro arquivo. Por exemplo, em um arquivo de log isso é comum, pois algumas mensagens de saída podem ser mais interessantes que outras para a análise de um problema.

Você também pode usar um comando global para obter um grupo de linhas que segue algum padrão e anexar todas elas a um arquivo externo. Por exemplo, se quiser capturar todas as linhas que possuem a mensagem ERROR:

* g/ERROR/.W erros.txt

Note, nesse caso, o uso do endereço atual (.) antes do comando. Sem ele, todas as linhas do arquivo serão escritas para cada ocorrência detectada. Portanto precisamos especificar que se deseja gravar apenas a linha atual (isto é, a linha onde o padrão foi detectado).

Alternativas dentro do padrão

Para obter o mesmo efeito, podemos usar um redirecionamento para um comando que apenas emite a linha, e usar um redirecionamento do próprio shell para adicionar essas linhas ao arquivo de destino.

* g/ERROR/.w !cat >> erros.txt

Endereço percentual

No lugar da vírgula (,), podemos usar o endereço na forma de percentual (%), com o mesmo efeito.

Alternativas dentro do padrão

Nem mesmo vejo motivo para preferir o percentual nesse caso (que provavelmente está implementado assim por razões de retrocompatibilidade), basta usar a vírgula.

Uso não interativo

Apesar de o ponto forte do ed ser o uso interativo, ele também pode ser usado como um interpretador (dos comandos dele) em modo não interativo. Neste caso, você define um conjunto de instruções que ele deve executar, e usa como entrada do programa.

Vamos começar com um exemplo descartável, isto é, apenas redirecionar algum comando para o ed e ver a saída na tela:

$ echo ',n' | ed -p '' poema_ed.txt 
85
1       Editor minimalista
2       nunca te deixa na mão,
3       seja em PC de batata
4       ou em PC de milhão.
$ 

Neste caso, usamos o comando echo para emitir um comando aceito pelo ed (,n), redirecionando esse comando para o ed, que inicia com um prompt vazio (do contrário ele apareceria na tela também, atrapalhando a saída), e abrindo o arquivo poema_ed.txt no buffer.

Ao abrir o arquivo, a contagem de bytes é apresentada, seguida do resultado do comando enviado, que mostra o arquivo com a numeração das linhas. Podemos suprimir a contagem de bytes e demais mensagens de diagnósticos, o que é útil para o uso não interativo. Para isso, usamos o parâmetro -s na chamada ao ed:

$ echo ',n' | ed -sp '' poema_ed.txt
1       Editor minimalista
2       nunca te deixa na mão,
3       seja em PC de batata
4       ou em PC de milhão.
$ 

Também é possível passar mais de um comando, desde que a mensagem emitida pelo programa echo, ou seja qual for o que usar, separe os comandos por quebras de linha.

$ echo ',n\n!rev %' | ed -sp '' poema_ed.txt
1       Editor minimalista
2       nunca te deixa na mão,
3       seja em PC de batata
4       ou em PC de milhão.
rev poema_ed.txt
atsilaminim rotidE
,oãm an axied et acnun
atatab ed CP me ajes
.oãhlim ed CP me uo
$ 

Rapidamente você percebe que executar os comandos dessa forma vai ficando desconfortável, quanto maior for o número de instruções. Então uma forma de lidar com isso é salvar as instruções em um arquivo, e então ler esse arquivo.

$ echo ',n\n!rev %' > num_rev.ed
$ ed -sp '' poema_ed.txt < num_rev.ed
1       Editor minimalista
2       nunca te deixa na mão,
3       seja em PC de batata
4       ou em PC de milhão.
rev poema_ed.txt
atsilaminim rotidE
,oãm an axied et acnun
atatab ed CP me ajes
.oãhlim ed CP me uo

E você não precisa se limitar a comandos de leitura apenas, mas pode usar também comandos que criam e modificam arquivos, do mesmo modo como usaria no modo interativo.

Vejamos outro exemplo, usando o script a seguir, que cria e altera um arquivo.

script.ed
f teste.txt
a
esta linha será modificada
logo em seguida
.
1s/será/foi
w
q

Este comando criará o arquivo teste.txt, adicionará um texto e em seguida alterará a primeira linha. Vejamos isto em ação:

$ ed -sp '' < script.ed
teste.txt
esta linha foi modificada
$ cat teste.txt
esta linha foi modificada
logo em seguida
$ 

Veja que o resultado final, quando exibimos o conteúdo do arquivo, já mostra a alteração aplicada. E perceba também que nesse caso nem precisei informar um arquivo a ser aberto, pois o buffer nesse caso é aberto vazio, e o arquivo destino é definido dentro do script.

Para o uso não interativo, é recomendável iniciar pelo comando H, que mencionei no início do tutorial. Recapitulando, ele ativa as mensagens de diagnóstico. Como se trata de uma execução de vários comandos em lote, você pode querer que possíveis mensagens de erro que ocorrerem sejam detalhadas enquanto ele executa, algo que no modo interativo você pode se dar o luxo de dispensar se souber o que está fazendo.

O uso não interativo mostra a versatilidade desse editor, que pode, em muitas circunstâncias, ser usado para obter os mesmos resultados que outros comandos, como cat, grep e sed, e usando a execução de comandos dele, outros tantos usos podem ser feitos.

Ainda assim, aponto para o fato de que essas outras ferramentas não foram criadas por acaso. Para o uso não interativo, você pode fazer muito mais com o auxílio de ferramentas como awk e sed.

E agora?

Bom, se você gostou do editor ed, sua jornada com ele está apenas começando. Você precisará praticar para pegar o jeito, mas com o que viu até aqui, já conseguirá se virar muito bem com ele. Minha intenção com esse artigo era que você pudesse vê-lo não como um editor meia boca para quando não tem uma opção melhor, mas sim como uma ferramenta poderosíssima à sua disposição, quando você precisar dela.

E o melhor, não há mais nenhuma outra funcionalidade (ao menos dentro do padrão) que não tenha sido abordada aqui. Se estivéssemos tratando de qualquer editor mais complexo, um tutorial cobrindo todos os aspectos do editor ficaria inviável. Mas no caso do ed, ele tem apenas o mínimo necessário, o que possibilita uma cobertura completa das funcionalidades dele em relativamente pouco tempo.

Boa edição!

História

Bem, agora que você já sabe usar o editor, talvez queira saber um pouco mais sobre sua história.

O editor ed foi um dos primeiros e mais importantes programas do sistema UNIX. Ele foi originalmente criado por Ken Thompson (em 1969), posteriormente com contribuições significativas de Dennis Ritchie, ambos conhecidos por uma importante participação autoral no sistema UNIX.

Cabe dizer também que a versão em uso hoje na maior parte dos sistemas que o incluem não é a mesma usada naquela época. Mas por ter se tornado parte do padrão POSIX, seu comportamento se manteve consistente entre diferentes implementações.

Padronização

O ed é parte do padrão POSIX. Presente nas seguintes revisões:

Implementações

Existem diferentes implementações do ed. Algumas delas são:

Como reflexo da simplicidade deste editor, todas as implementações acima, exceto a do Busybox, aderem totalmente ao proposto no padrão POSIX, e a maioria delas inclui também algumas funcionalidades fora do padrão, como paginadores e atalhos para repetir substituições, dentre outras.

utils/ed.txt · Última modificação: 2025/01/19 13:26 por hrcerq