ed, l'editor del Vero Programmatore

Carissima, spero che tu abbia passato buone vacanze, che ti sia divertita e che sia pronta a continuare a leggere questi miei articoli. D'altra parte, posso anche presumere che dopo la fatica delle ferie non sia proprio la migliore delle cose l'avere uno di quegli articoli tosti da digerire. O per meglio dire, io sono abbastanza stanco da avere deciso di prendermi in questo numero un po' di riposo. Visto che però il Direttore di BETA non avrebbe mai ritenuto sufficienti le mie giustificazioni, ho pensato che forse scrivere qualcosa occorreva, dopotutto. Dopo lunga meditazione, ho infine deciso che parlare di ed(1) era la soluzione che salva capra e cavoli.

Nel caso non lo sapessi, ed(1) è il primo editor storicamente apparso nei sistemi Unix, quando era follia anche solo pensare che più utenti contemporaneamente potessero utilizzare vi(1), consumando in tal modo tutte le risorse del calcolatore[1]. Occorreva quindi qualcosa di leggero[2], e nacque così ed(1): l'idea è la stessa che una dozzina di anni dopo verrà implementata in MS-DOS sotto il nome di EDLIN, anche se naturalmente quest'ultimo è semplicemente un sottinsieme del Vero Editor.

Se non sei già scappata disgustata, ti chiederai perché mai io debba scrivere di un rudere archeologico, e soprattutto perché tu debba leggerne: domande perfettamente legittime, cui tenterò di dare alcune risposte. A te decidere se sono sufficienti.

  1. Innanzitutto, non è detto che non ti capiti di doverlo usare. Alcune implementazioni dei boot disk di linux che mi sono capitate non avevano affatto vi(1) disponibile, e perciò l'unico modo di editare una configurazione era utilizzare il buon vecchio ed(1)[3].
  2. Se vuoi definirti una Vera Programmatrice, devi dimostrare all'attonito pubblico che conosci il Vero Editor, almeno quel tanto che basta per chiamarlo e uscirne senza spegnere la macchina. Non scherzo: la "consistente interfaccia" del programma ti segnala ogni errore nello stesso e identico modo (un "?") e sono stati segnalati alcuni casi di atteggiamenti rasenti la pazzia per l'incapacità di scoprire come uscire dall'editor. E dopo tutto, può sempre capitarti, almeno per sbaglio, di digitare i tre caratteri "e", "d", "" nell'ordine...
  3. Imparare i comandi di ed(1) è infine utile non tanto di per sé, quanto per portarsi avanti col lavoro per quando uno deve utilizzare sed(1), lo streaming editor che sicuramente è davvero utile e sarà prima o poi oggetto della mia trattazione. Visto che io sono notoriamente pigro, quando giungerà il momento di parlare di sed(1), glisserò amabilmente sui comandi, scrivendo "tanto sono uguali a quelli di ed(1)", tranne naturalmente le eventuali eccezioni. A questo punto dovrai lanciarti a recuperare il numero arretrato, e sperare che non sia andato esaurito. Vuoi correre il rischio?
  4. Se hai delle velleità da cracker, puoi sempre sperare che il sysadm del sito remoto si sia dimenticato di togliere il programma :-)

Dimenticavo: se ti interessa davvero avere il parere (ahimé, in inglese!) di un Vero Programmatore sull'argomento ti invito a dare un'occhiata all'URL http://www.flail.com/humour/ed. Sante parole.

L'interfaccia di ed

Come ti avevo detto, l'interfaccia di ed(1) è molto consistente. Infatti, non ti viene quasi mai scritto nulla: l'impeto di verbosità quando si apre un file - viene indicata la dimensione del file stesso in byte - è molto più di quanto capiti di solito. Naturalmente, se fai un errore, ti viene specificato un simpatico messaggio: l'autoesplicativo ?.[4] La logica è lampante: un Vero Programmatore sa sempre cosa sta facendo e, nel malaugurato caso di un errore di battitura, gli basta l'avviso di errore per capire cosa aveva sbagliato. In realtà, se proprio ti senti un poco a disagio perché sei un'Aspirante Vera Programmatrice, hai la possibilità di sapere qual era l'errore: il comando h dà infatti una (breve!) spiegazione di cosa hai fatto. Il comando H ti permette di vedere automaticamente questi messaggi: se la cosa ti scoccia, puoi eseguirlo un'altra volta e si ritorna nella situazione iniziale. Un altro toggle è il comando P. Questo ti permette di avere un prompt (di default un asterisco *, ma se usi l'opzione -p prompt sarà quest'ultimo ad essere utilizzato) prima di ogni comando.

Come hai visto, i comandi sono composti da una sola lettera, per economia. In realtà, il comando può spesso essere preceduto da uno o due indirizzi; quando si ha un indirizzo, questo è quello da usare, mentre se ce ne sono due viene indicato l'insieme di righe (il range) da usare. I due indirizzi possono essere separati da una virgola "," oppure da un puntoevirgola ";": la differenza tra le due forme è data dalla definizione di riga corrente. Questa, indicata con un punto ".", è usata per tutti gli indirizzi: è anche possibile indicare una riga calcolata rispetto a quella corrente, sommando o sottraendo un valore. Ora, se si usa la virgola la riga corrente è sempre la stessa, mentre se si usa un puntoevirgola questa viene ricalcolata prima di decidere qual è il secondo indirizzo. Un esempio pratico: se io sono posizionato alla riga 20 e scrivo .+10,.+5 il nostro Vero Editor si impunta, perché non ha senso che si parta dalla riga 30 per arrivare alla 25; se invece avessi scritto .+10;.+5 non ci sarebbero stati problemi, perché il range sarebbe stato dalla riga 30 alla 35.

Mentre si parla di indirizzamento, non è stupido fare notare che l'ultima riga del file si può indicare con un dollaro "$", e che è anche possibile usare la virgola da sola prima del comando: in questo caso si suppone che tu voglia lanciare il comando su tutte le righe del testo, come se si fosse scritto "1,$". L'ultima possibilità per l'indirizzamento consiste nel marcare delle righe più o meno come in vi(1), con il comando kx, dove x è una lettera minuscola; la lettera k sta per mark. A questo punto, per identificare quella riga basterà scrivere 'x.

Alcuni comandi possono infine essere seguiti da un parametro: ad esempio, per salvare il testo sul file pippo occorre scrivere w pippo, come vedremo più sotto.

L'ultima nota riguarda naturalmente il comando di undo, u. Come si può intuire, esso ti permette di cancellare l'effetto dell'ultimo comando che ha modificato il buffer, scrivendo o cancellando qualcosa: anche lo stesso undo. Questo significa naturalmente che non hai un livello infinito di undo, mi spiace dirtelo.

Visualizzazione

Se non lo avessi ancora capito, ed(1) non è un editor visuale: insomma, non vedi cosa c'è scritto nel testo. Se proprio ritieni che sia necessario darci un'occhiata, non hai a disposizione un comando, e nemmeno due, ma ben tre!

Puoi infatti utilizzare il comando p (print), eventualmente preceduto da un range di righe, per stampare senza nessun fronzolo le righe che ti servono; oppure n, (number print) che stampa le righe mettendoci a fianco il numero relativo; o ancora l, (list print) che stampa le righe in maniera cosiddetta "visibile" (vale a dire, trasformando i caratteri non stampabili in sequenze ottali oppure con le sequenze di escape tipo "\t", andando a capo con un backslash "\" e infilando un bel dollaro "$" alla fine di ogni riga. Il bello di questi tre comandi è poi che possono essere piazzati dopo ogni altro comando civile[5]. È divertente ad esempio scrivere 1,5nl, (oppure 1,5ln...), visto che il tab tra il numero di riga e la riga stessa appare regolarmente come un \t. Se vuoi sapere quante righe ci sono nel file, puoi invece usare il comando =, che stampa per l'appunto il numero d'ordine dell'ultima riga.[6]

Inserimento, cancellazione, spostamento

Come forse ti è noto per avere usato vi(1), anche ed(1) può essere in due modalità diverse: command mode e input mode. In quest'ultimo, tutto quello che viene scritto è considerato come testo: per potere tornare a dare comandi, occorre scrivere su una riga i due caratteri ".".

Per aggiungere del testo ci sono due possibilità principali: il comando i (insert) e il comando a (append). Entrambi possono essere preceduti da un indirizzo, in modo da posizionarsi in un posto diverso da dove ci si trova: la differenza tra i due comandi è che i inserisce del testo (e quindi lo mette prima della posizione attuale) mentre a lo aggiunge, mettendolo dopo. In questo caso è possibile indicare 0 come indirizzo: "0a" inserisce il testo prima della prima riga (cioè dopo la zeresima!).

È anche possibile cancellare delle righe, con il comando d (delete), oppure sostituirne alcune, con il comando c (change): in quest'ultimo caso prima vengono cancellate le righe comprese tra gli indirizzi indicati, poi si aggiunge il testo lì in mezzo. È chiaro che con questi due comandi si possono usare uno o due indirizzi: per tutti i comandi di questo paragrafo, il default per l'operazione è la riga corrente.

In tutti i casi, la riga corrente diverrà l'ultima ad essere stata scritta, a meno che non se ne scriva nessuna: in tal caso, essa sarà quella immediatamente successiva.

Per spostare il testo, si utilizza il comando m (move), che naturalmente può volere fino a tre indirizzi: uno o due prima del comando stesso, per indicare la riga da muovere, e uno dopo, per indicare dopo quale riga si vuole posizionare il tutto. Come hai già intuito, quest'ultimo indirizzo può anche essere la "riga 0": se scrivo 'a,'bm0 voglio mettere in cima al file quanto compreso tra le righe marcate con a e b. Un comando simile è t, che però copia le righe, invece che spostarle solamente[7].

L'ultimo comando in questa sezione è j (join), che ti permette di unire tra di loro un certo numero di righe consecutive, quelle indicate nel range a sinistra del comando. Evita pertanto di scrivere 5j, perché avresti semplicemente sprecato tre caratteri[8] inutilmente, dato che unire le righe dalla 5 alla 5 non dà molti risultati.

Rimpiazzi e sostituzioni

Per quanto piccolo possa essere, ed(1) è chiaramente stato studiato per facilitare le sostituzioni all'interno di un file. Il comando principale per le sostituzioni è, manco a dirlo, s: la sua sintassi completa è
[range]s/RE/rimpiazzo/[modificatore]
dove le parti tra parentesi quadre sono naturalmente opzionali.

Vediamo con calma il tutto: cos'è il range già lo sai, e la "s" è il comando. RE è l'espressione regolare da sostituire; le barre servono per delimitarla, e al loro posto si può usare in realtà un qualunque carattere che non sia lo spazio. Se proprio ti serve usare quel carattere all'interno dell'espressione regolare, basta farlo precedere da un backslash "\". Il rimpiazzo è quello che verrà messo al posto dell'espressione regolare: si può infilare l'espressione stessa con il carattere ampersand "&", l'ennesima sottoespressione regolare (messa tra parentesi tonde "\(" e "\)" può venire indicata con "\n", e se il rimpiazzo consiste nell'unico carattere "%", allora viene usato il rimpiazzo precedente. Inutile dire che tutti questi caratteri speciali possono essere usati per il loro valore facciale facendoli precedere da un backslash: quest'ultimo, messo a fin di riga, serve per andare a capo.

I modificatori sono p, n, l che stampano le righe sostituite con il formato del corrispondente comando, g che per ogni riga fa la sostituzione non solo sulla prima occorrenza dell'espressione regolare ma su tutte, e n, dove n è un numero, che la fa sull'n-sima occorrenza nella riga. In ogni caso, la riga corrente diventa l'ultima dove è stata fatta una modifica.

È poi possibile eseguire un comando qualunque, e non solo una sostituzione, sulle righe che corrispondono all'espressione regolare: per fare ciò, si usa il comando global, la cui sintassi è
[range]g/RE/lista di comandi
Con questo comando (che tra l'altro, essendo globale, ha come range di default tutto il file) vengono eseguiti i comandi della lista. Ad esempio, il comando g/./m0 sposta tutte le righe che hanno almeno un carattere all'inizio del file, rigorosamente alla rovescia visto che la ricerca è lineare: il risultato pratico, se non ci sono righe vuote, è rovesciare l'ordine delle righe nel file.

Ancora più libertà è data dal comando G: in questo caso la lista di comandi non è presente, perché ed(1) si ferma ad ogni riga che contiene la nostra espressione regolare e ci permette di scrivere il comando da fare. Se ci va bene il comando precedente, basta scrivere &. Se ti sei scocciato di scrivere comandi, un bel SIGINT (Ctl-D, se non hai toccato nulla) termina il comando.

Dulcis in fundo, vuoi fare le modifiche a tutte le righe che non corrispondono all'espressione regolare? Niente di più facile: usi rispettivamente v e V.

Salvataggio e uscita

Per terminare, ti ricordo che per uscire da ed(1) ci sono due possibilità: i comandi q e Q. Entrambi stanno per quit: la differenza è che nel primo caso ti viene data una segnalazione di errore se hai fatto delle modifiche dall'ultima volta che hai salvato il testo, mentre nel secondo si esce e basta.

E come faccio a salvare il testo, ti starai chiedendo? Con i comandi w e W (write). Entrambi prevedono di salvare tutto il buffer, oppure la parte indicata tra gli indirizzi, nel file indicato eventualmente come parametro oppure nel file "ricordato". Verrà scritto come output il numero di caratteri scritti. È possibile cambiare nome al file "ricordato" con il comando f nome_file.

Per l'operazione opposta al comando w, vale a dire il leggere un file, il comando da usare è r file (read). È possibile dire a che riga si vuole inserire il file letto: va bene anche la riga 0 che indica che si vuole inserirlo prima del nostro file. La riga corrente diventa l'ultima ad essere stata letta. Se invece si voleva proprio cambiare file, i comandi da utilizzare sono e file oppure E file (edit). Non dovrei nemmeno stare a spiegarti la differenza: nel primo caso. se non hai salvato il buffer corrente l'editor ti spernacchia, altrimenti butta amabilmente via il tutto. La riga corrente è l'ultima del file: il file aperto diventa quello "ricordato".

Shell

Molti comandi permettono di usare una shell al posto di un nome di file: la shell dovrebbe essere rigorosamente /bin/sh ma in realtà alcune versioni permettono di usare /bin/ksh e ho come il sospetto che la versione GNU usi /bin/bash. Ovviamente il comando principale è !comando, che esegue il comando suindicato. Al suo interno, si può usare un percento "%" per indicare il nome del file corrente oppure si può scrivere !! per ripetere l'ultimo comando. Il nostro Vero Editor, sempre pronto a dirci tutto quello che facciamo, risponderà con il testo "!\n", a meno che noi preferiamo non avere tutta questa verbosità e utilizziamo l'opzione -s quando lanciamo ed(1).

È però possibile utilizzare la shell anche nel contesto di altri comandi. Ad esempio, se lo si fa con i comandi w e W il testo dopo il punto esclamativo viene preso come il comando cui dare in pasto il testo selezionato, che verrà preso come standard input. Similmente, se viene usato dopo il comando r il testo dopo l'esclamativo sarà utilizzato come comando, e il risultato di tale comando sarà inserito nel nostro testo; se lo si usa dopo e oppure E, verrà editato il risultato della nostra shell[9].

Quello che non serve

Accenno infine molto in fretta ad alcune possibilità date almeno dalla versione Sun di ed(1), che non ritengo utili in pratica. Almeno sappi però che esistano.

Note & chiose

[1]
Che non erano poi molte, come puoi immaginare. Altro che emacs(1)!
[2]
Sulla mia workstation Solaris, l'eseguibile di ed(1) è lungo 49384 byte, contro i 227768 di vi. Sul mio PC con linux, le dimensioni sono rispettivamente 68028 (sai, le versioni GNU sono sempre esagerate...) e 294812. Non ho spazio disco sufficiente per installare emacs.
[3]
Tra l'altro, questa è la ragione per cui per una volta parlo del comando standard, senza accennare alle estensioni GNU. La più importante di queste è la possibilità di editare file binari, con trucchetti vari (leggere /dev/null ...) per evitare che vengano aggiunti dei caratteri \n spurii.
[4]
Come? non è chiaro che hai fatto un errore?
[5]
La definizione di "comando civile" è un qualunque comando che non faccia input/output, vale a dire tutti meno e, E, f, q, Q, r, w, W, !.
[6]
Di per sé puoi anche scrivere ad esempio 5=: ti viene semplicemente stampato il numero della quinta linea, cioè 5. Se hai un file abbastanza lungo, puoi permetterti il lusso di far fare a ed(1) le addizioni: ad esempio, 5+6= ti risponderà 11 :-).
[7]
No, la t non è per nulla mnemonica. Probabilmente era la prima lettera disponibile che si sono trovati a disposizione.
[8]
C'è anche il finale.
[9]
Opzione non del tutto peregrina. Pensa a un comando come ls -t | head -1 in una directory dove vengono creati più o meno regolarmente dei nuovi file.