1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
|
# ed — Line Editor
## Overview
`ed` is the standard POSIX line editor. It operates on a text buffer that
resides in a temporary scratch file, supports regular expression search and
substitution, global commands, undo, and file I/O. This implementation derives
from Andrew Moore's BSD `ed` and the algorithm described in Kernighan and
Plauger's *Software Tools in Pascal*.
**Source**: `ed/main.c`, `ed/ed.h`, `ed/compat.c`, `ed/compat.h`, `ed/buf.c`,
`ed/glbl.c`, `ed/io.c`, `ed/re.c`, `ed/sub.c`, `ed/undo.c`
**Origin**: BSD 4.4, Andrew Moore (Talke Studio)
**License**: BSD-2-Clause
## Synopsis
```
ed [-] [-sx] [-p string] [file]
```
## Options
| Flag | Description |
|------|-------------|
| `-` | Suppress diagnostics (same as `-s`) |
| `-s` | Script mode: suppress byte counts and `!` prompts |
| `-x` | Encryption mode (**not supported on Linux**) |
| `-p string` | Set the command prompt (default: no prompt) |
## Source Architecture
### File Responsibilities
| File | Purpose | Key Functions |
|------|---------|---------------|
| `main.c` | Main loop, command dispatch, signals | `main()`, `exec_command()`, signal handlers |
| `ed.h` | Types, constants, function prototypes | `line_t`, `undo_t`, error codes |
| `compat.c/h` | Linux portability shims | `strlcpy`, `strlcat` replacements |
| `buf.c` | Scratch file buffer management | `get_sbuf_line()`, `put_sbuf_line()` |
| `glbl.c` | Global command (g/re/cmd) | `exec_global()`, mark management |
| `io.c` | File I/O (read/write) | `read_file()`, `write_file()`, `read_stream()` |
| `re.c` | Regular expression handling | `get_compiled_pattern()`, `search_*()` |
| `sub.c` | Substitution command | `substitute()`, replacement parsing |
| `undo.c` | Undo stack management | `push_undo_stack()`, `undo_last()` |
### Key Data Structures
#### Line Node (linked list element)
```c
typedef struct line {
struct line *q_forw; /* Next line in buffer */
struct line *q_back; /* Previous line in buffer */
off_t seek; /* Byte offset in scratch file */
int len; /* Line length */
} line_t;
```
The edit buffer is a doubly-linked circular list of `line_t` nodes. Line
content is stored in an external scratch file, not in memory.
#### Undo Record
```c
typedef struct undo {
int type; /* UADD, UDEL, UMOV, VMOV */
line_t *h; /* Head of affected line range */
line_t *t; /* Tail of affected line range */
/* ... */
} undo_t;
```
#### Constants
```c
#define ERR (-2) /* General error */
#define EMOD (-3) /* Buffer modified warning */
#define FATAL (-4) /* Fatal error (abort) */
#define MINBUFSZ 512
#define SE_MAX 30 /* Max regex subexpressions */
#define LINECHARS INT_MAX
```
#### Global Flags
```c
#define GLB 001 /* Global command active */
#define GPR 002 /* Print after command */
#define GLS 004 /* List after command */
#define GNP 010 /* Enumerate after command */
#define GSG 020 /* Global substitute */
```
### Main Loop
```c
int main(volatile int argc, char **volatile argv)
{
setlocale(LC_ALL, "");
/* Detect if invoked as "red" (restricted ed) */
red = (n = strlen(argv[0])) > 2 && argv[0][n - 3] == 'r';
/* Parse options */
while ((c = getopt(argc, argv, "p:sx")) != -1) { ... }
/* Signal setup */
signal(SIGHUP, signal_hup); /* Emergency save */
signal(SIGQUIT, SIG_IGN); /* Ignore quit */
signal(SIGINT, signal_int); /* Interrupt handling */
signal(SIGWINCH, handle_winch); /* Terminal resize */
/* Initialize buffers, load file if specified */
init_buffers();
if (argc && is_legal_filename(*argv))
read_file(*argv, 0);
/* Command loop */
for (;;) {
if (prompt) fputs(prompt, stdout);
status = get_tty_line();
if (status == EOF) break;
status = exec_command();
}
}
```
### Buffer Management (buf.c)
The scratch file strategy avoids unlimited memory consumption:
- Lines are stored in a temporary file created with `mkstemp(3)`
- `put_sbuf_line()` appends a line to the scratch file and returns its offset
- `get_sbuf_line()` reads a line back from the scratch file by offset
- The `line_t` linked list tracks offsets and lengths, not actual text
```c
/* Append line to scratch file, return its node */
line_t *put_sbuf_line(const char *text);
/* Read line from scratch file via offset */
char *get_sbuf_line(const line_t *lp);
```
### File I/O (io.c)
```c
long read_file(char *fn, long n)
{
/* Open file or pipe (if fn starts with '!') */
fp = (*fn == '!') ? popen(fn + 1, "r") : fopen(strip_escapes(fn), "r");
/* Read lines into buffer after line n */
size = read_stream(fp, n);
/* Print byte count unless in script mode */
if (!scripted)
fprintf(stdout, "%lu\n", size);
}
```
The `read_stream()` function reads from `fp`, appending each line to the
edit buffer via `put_sbuf_line()`, and maintaining the undo stack for
rollback.
### Regular Expressions (re.c)
Uses POSIX `regex.h` (via `regcomp(3)` / `regexec(3)`):
```c
typedef regex_t pattern_t;
pattern_t *get_compiled_pattern(void);
/* Compiles the current regex pattern, caching the last used pattern */
```
### Substitution (sub.c)
The `s/pattern/replacement/flags` command:
- Supports `\1` through `\9` backreferences
- `g` flag for global replacement
- Count for nth occurrence replacement
- `&` in replacement refers to the matched text
### Undo (undo.c)
Every buffer modification pushes an undo record:
```c
undo_t *push_undo_stack(int type, long from, long to);
int undo_last(void); /* Reverse last modification */
```
Undo types: `UADD` (lines added), `UDEL` (lines deleted), `UMOV` (lines moved).
### Signal Handling
| Signal | Handler | Action |
|--------|---------|--------|
| `SIGHUP` | `signal_hup()` | Save buffer to `ed.hup` and exit |
| `SIGINT` | `signal_int()` | Set interrupt flag, longjmp to command prompt |
| `SIGWINCH` | `handle_winch()` | Update terminal width for `l` command |
| `SIGQUIT` | `SIG_IGN` | Ignored |
### Restricted Mode (red)
When invoked as `red`, the editor restricts:
- Shell commands (`!command`) are forbidden
- Filenames with `/` or starting with `!` are rejected
- Directory changes are prevented
## Commands Reference
| Command | Description |
|---------|-------------|
| `(.)a` | Append text after line |
| `(.)i` | Insert text before line |
| `(.,.)c` | Change (replace) lines |
| `(.,.)d` | Delete lines |
| `(.,.)p` | Print lines |
| `(.,.)l` | List lines (show non-printable characters) |
| `(.,.)n` | Number and print lines |
| `(.,.)m(.)` | Move lines |
| `(.,.)t(.)` | Copy (transfer) lines |
| `(.,.)s/re/replacement/flags` | Substitute |
| `(.,.)g/re/command` | Global: apply command to matching lines |
| `(.,.)v/re/command` | Inverse global: apply to non-matching lines |
| `(.,.)w file` | Write lines to file |
| `(.,.)W file` | Append lines to file |
| `e file` | Edit file (replaces buffer) |
| `E file` | Edit unconditionally |
| `f file` | Set default filename |
| `r file` | Read file into buffer |
| `(.)r !command` | Read command output into buffer |
| `u` | Undo last command |
| `(.)=` | Print line number |
| `(.,.)j` | Join lines |
| `(.)k(c)` | Mark line with character c |
| `q` | Quit (warns if modified) |
| `Q` | Quit unconditionally |
| `H` | Toggle verbose error messages |
| `h` | Print last error message |
| `!command` | Execute shell command |
### Addressing
| Address | Meaning |
|---------|---------|
| `.` | Current line |
| `$` | Last line |
| `n` | Line number n |
| `-n` / `+n` | Relative to current line |
| `/re/` | Next line matching regex |
| `?re?` | Previous line matching regex |
| `'c` | Line marked with character c |
| `,` | Equivalent to `1,$` |
| `;` | Equivalent to `.,$` |
## System Calls Used
| Syscall | Purpose |
|---------|---------|
| `mkstemp(3)` | Create scratch file |
| `read(2)` / `write(2)` | Scratch file I/O |
| `lseek(2)` | Position in scratch file |
| `fopen(3)` / `fclose(3)` | Read/write user files |
| `popen(3)` / `pclose(3)` | Shell command execution |
| `regcomp(3)` / `regexec(3)` | Regular expression matching |
| `sigsetjmp(3)` / `siglongjmp(3)` | Interrupt recovery |
## Examples
```sh
# Edit a file
ed myfile.txt
# Script mode (for automation)
printf '1,3p\nq\n' | ed -s myfile.txt
# Global substitution
printf 'g/old/s//new/g\nw\nq\n' | ed -s myfile.txt
# With prompt
ed -p '> ' myfile.txt
# Read from pipe
echo '!ls -la' | ed
```
## Exit Codes
| Code | Meaning |
|------|---------|
| 0 | Success |
| 1 | Error |
| 2 | Usage error |
## Linux-Specific Notes
- The `-x` (encryption) option prints an error and exits, as Linux
does not provide the BSD `des_setkey(3)` functions.
- `strlcpy(3)` / `strlcat(3)` are provided by `compat.c` when not
available in the system libc.
- The `SIGWINCH` handler uses `ioctl(TIOCGWINSZ)` for terminal size.
|