Hintergrund
Ed kommt aus einer Welt, in der es noch keine Bildschirme gab. Die Ausgabe wurde auf einem Zeilendrucker (meist ein Typenraddrucker, der auf Endlospapier geschrieben hat) ausgegeben. Dieser Drucker war mit der Tastatur zu einem Terminal verbaut. Da diese Drucker langsam waren (10-30 Zeichen pro Sekunde), waren lange Ein- und Ausgaben langsam. Folglich ist in ed alles so kurz wie moeglich, damit der Programmierer moeglichst wenig auf den Drucker warten muss.
Das praegnanteste Beispiel sind eds Fehlerausgaben, die in jedem Fall nur aus einem einzigen Zeichen bestehen: `?'
Auch alle Befehle bestehen nur aus einem einzigen Buchstaben.
Trotz dieser damaligen Einschraenkungen, seines Alters und der Tatsache, dass ed gerade mal 2500 Zeilen C-Code umfasst, ist ed erstaunlich maechtig. Mit ihm lassen sich manche Aufgaben bewaeltigen, fuer die sonst programmiert werden muesste.
Installation und einfache Benutzung
Am einfachsten ist es, den GNU ed als Debian-Paket zu installieren: ed.
Ich verwende lieber die Heirloom Implementierung des ed. Diese wird als Teil der Heirloom Tools ausgeliefert: https://heirloom.sourceforge.net/tools.html. Weil es mir zu umstaendlich war, jeweils die ganze Toolchest zu uebersetzen, wenn ich nur einen ed wollte, habe ich eine Standalone-Version extrahiert: http://hg.marmaro.de/heirloom-ed/
Ed wird mit einem optionalen Dateinamensargument gestartet ... und dann sieht man erstmal nichts und weiss nicht was man tun soll und ist aufgeschmissen.
Zwei Befehle machen es Anfaengern einfacher:
- `P' (prompt) aktiviert einen Prompt (das einzelne Zeichen `*'), wodurch man sieht, wann man Befehle eingeben kann.
- `H' (help) aktiviert die Ausgabe menschenlesbarer Fehlermeldungen, so dass nicht nur `?' ausgegeben wird.
Beide Befehle sind Toggle und nach einmaliger Eingabe bis zum Ende der Editiersession aktiv.
Beendet wird ed mit dem Befehl `q' (quit). Falls man Aenderungen in der Datei gemacht hat, ohne diese zu speichern, dann beendet ed nicht sofort (weil man die Aenderungen sonst ja verlieren wuerde). Stattdessen fragt er lieb nach (d.h. er sagt `?') und beendet nur wenn man nochmals den `q' Befehl zur Bestaetigung eingibt.
Code: Alles auswählen
$ ed
P
*H
* [... Aenderungen an der Datei ...]
*q
?
warning: expecting `w'
*q
$
Die groesste Schwierigkeit bei ed ist sicherlich, diese verschiedenen Ebenen zu unterscheiden und zu wissen, wo man sich gerade befindet. Ein aktivierter Prompt in ed hilft schon eine Menge. Gut zu wissen ist auch, dass man mit ^C (Strg + C) immer zu einem frischen ed-Prompt zurueckkommt und das vorige Kommando abgebrochen wird. (Das ist wie in der Shell: ^C bricht ab und bringt einen zurueck zum Eingabeprompt.)
Text eingeben
Mit dem `a' Befehl (append) wechselt man in den Eingabemodus. Alles was nun eingegeben wird, ist Inputtext fuer die Datei, der nach der aktuellen Zeile eingefuegt wird. (Bei einer leeren Datei befindet man sich in Zeile 0.) Man beendet die Eingabe, indem man eine Zeile eingibt, die nur genau einen Punkt enthaelt:
Code: Alles auswählen
$ ed
P
*H
*a
foo bar
baz
.
*
Speichern koennen wir mit `w' und dem Dateinamen:
Code: Alles auswählen
*w foo
12
*
Code: Alles auswählen
*q
$ cat foo
foo bar
baz
$ wc -c foo
12 foo
Nun koennen wir die Datei `foo' mit ed wieder oeffnen und uns in ed ihren Inhalt anzeigen lassen:
Code: Alles auswählen
$ ed foo
12
P
*H
*,p
foo bar
baz
*
`,p' ist der Befehl, der die ganze Datei ausgibt. Zur Ausgabe dient der Befehl `p' (print).
Adressen
Jedem Befehl koennen Zeilenadressen vorangestellt werden, die angeben, auf welche Zeilen der Befehl wirken soll.
Wollen wir nur die zweite Zeile ausgeben, so geht das mit `2p':
Code: Alles auswählen
*2p
baz
*
Code: Alles auswählen
*1a
blubber
blibber
blobber
.
*,p
foo bar
blubber
blibber
blobber
baz
*
Code: Alles auswählen
*1,3p
foo bar
blubber
blibber
*
Eine nummerierte Zeilenausgabe ist auch moeglich mit dem Befehl `n' (numbered), der einfach statt `p' verwendet werden kann:
Code: Alles auswählen
*3,$n
3 blibber
4 blobber
5 baz
*
Code: Alles auswählen
*3
blibber
*.=
3
*n
3 blibber
*
Adressen kann man nicht nur anhand der Zeilennummer angeben, sondern auch mittels regulaerer Ausdruecke. Fasst man den regulaeren Ausdruck mit Slashes (`/') ein, dann wird eine Vorwaertssuche von der aktuellen Zeile aus gemacht. Verwendet man Fragezeichen (`?'), dann wird rueckwaerts gesucht. Hier ein paar Beispiele fuer die Verwendung von regulaeren Ausdruecken als Adressen:
Code: Alles auswählen
*,n
1 foo bar
2 blubber
3 blibber
4 blobber
5 baz
*/baz/
baz
*n
5 baz
*?lub?
blubber
*n
2 blubber
*1,/obb/n
1 foo bar
2 blubber
3 blibber
4 blobber
*
Text bearbeiten
Gute Editoren sind fuer das Bearbeiten (nicht fuer das Schreiben) von Text optimiert, da dies viel mehr gemacht wird (oder werden sollte), als den Text nur mal einzugeben.
Wichtig ist es, zu realisieren, dass ed ein Zeileneditor ist. D.h. er arbeitet zeilenweise. Seine Adressierung ist anhand von Zeilen und fast alle Befehle bearbeiten Zeilen. Es gibt nur eine einzige Ausnahme: Nur das `s' Kommando (substitute) veraendert Teile einer Zeile.
Wie man Zeilen einfuegt, wissen wir schon: mit dem `a' Kommando (append). Es gibt auch ein `i' Kommando (insert), das genau das gleiche macht, nur *vor* der aktuellen bzw. angegebenen Zeile, statt danach.
Zeilen loeschen kann man mit dem `d' Kommando (delete). Es entfernt einfach die angegebenen Zeilen:
Code: Alles auswählen
*4d
*,n
1 foo bar
2 blubber
3 blibber
4 baz
*
Das `s' Kommando (substitute) veraendet Inhalte von Zeilen. Die meisten werden es von sed schon gut kennen:
Code: Alles auswählen
*3s/b/k/g
*p
klikker
*
Code: Alles auswählen
*s/k/c/2p
klicker
*
Scripting
Sed ist aus einer (in einer naechtlichen Aktion) modifizierten Version von ed entstanden. Sed erlaubt es, ed in Pipelines einzusetzen. Ed kann zwar nicht in Pipelines eingesetzt werden, aber er kann auch gescriptet werden. Das betrifft die Faelle, wo `sed -i' verwendet wird. Ueblicherweise uebergibt man die Befehle als Heredoc:
Code: Alles auswählen
$ ed - foo <<!
3d
10s/apfel/birne/
w
q
!
$
Ed hat einen Vorteil gegenueber `sed -i': ed kann mit Adressen rechnen. Adressen koennen nicht nur Zeilennummern sein, sondern auch regulaere Ausdruecke, die mittels Slashes (Vorwaertssuche) oder Fragezeichen (Rueckwaertssuche) begrenzt werden. Vorwaertssuchen gehen bei sed auch. Ed kann zudem mit allen Adressen rechnen, indem man mit Plus oder Minus Zahlen addiert oder subtrahiert. Die dritte Zeile kann man also auch so ausgeben:
Code: Alles auswählen
*1+2n
3 klicker
*4-1n
3 klicker
*4-n
3 klicker
*1++n
3 klicker
*3-----+++++n
3 klicker
*
Code: Alles auswählen
$ ed ed.c
44024
P
*H
*/^usage(/-;/^}/p
static void
usage(char c)
{
if (c) {
write(2, progname, strlen(progname));
write(2, ": illegal option -- ", 20);
write(2, &c, 1);
write(2, "\n", 1);
}
write(2, "usage: ", 7);
write(2, progname, strlen(progname));
write(2, " [-] [file]\n", 12);
exit(2);
}
*q
$
Entscheidend hier ist nun, dass die zweite Adresse nicht mit Komma sondern mit Strichpunkt abgetrennt ist. Der Unterschied ist, dass die zweite Adresse damit nicht von der aktuellen Zeile ausgehend gesucht wird, sondern von der vorherigen Adresse aus. Es wird also zuerst die erste Adresse gesucht (Start der usage()-Funktion) und von *dort* aus die zweite Adresse (Ende der Funktion).
Dieses Feature ist sehr maechtig.
Adressketten
Zum Abschluss ein besonderes Feature von ed: Adressketten. Dies kann kaum ein anderer Editor. Ed ist wie SQL im Vergleich zu Excel: Man denkt nach und schreibt einen maechtigen Befehl, statt von Hand zu arbeiten. Frueher war dies effektiv und oekonomisch. Heute ist stupide Handarbeit mit direktem Feedback eher gewuenscht, anstatt den Kopf zu bemuehen.
Jedenfalls kann man in ed nicht nur zwei Adressen angeben, sondern auch mehrere, die (mittels Strichpunkten) als Adresskette wirken, um nach und nach mit relativen Spruengen die passende Stelle zu finden. Ein abschliessender Befehl verwendet dann nur die letzten zwei Adressen.
So kann ich beispielsweise die naechste Nennung von `jmp' im Code hinter dem Start der main()-Funktion mit Kontext ausgeben:
Code: Alles auswählen
*/^main(/;/jmp/-3;+6n
294 sigset(SIGINT, onintr);
295 if (oldhup != SIG_IGN)
296 sigset(SIGHUP, onhup);
297 setjmp(savej);
298 if (lastsig) {
299 sigrelse(lastsig);
300 lastsig = 0;
*
Abschluss
Ich hoffe, dass ich hiermit einen kleinen Einstieg in ed geben konnte, der euch den Zugang erleichtert und vielleicht motiviert, mal eine Datei damit zu bearbeiten. Fuer weniger erfahrene User hat dieses Tuerchen vielleicht den Blick auf die Geschichte von Unix und Linux etwas erweitert. Fuer erfahrene User, die sich in der Shell heimisch fuehlen, ist dies vielleicht ein Anlass, um ed in das eigene Portfolio von Werkzeugen aufzunehmen.
Use ed once in a while!
Und zum Schluss noch der Klassiker: https://www.gnu.org/fun/jokes/ed-msg.html