Some day you will land on a box with a broken terminal, no vi and a config file that needs one line fixed, and ed will be the editor that still works. This guide takes about fifteen minutes and covers inspecting a file, changing a line, saving deliberately and running the same edit from a script.
The examples use GNU ed 1.20.1 from Debian package ed 1.20.1-1, installed on this machine. You need a shell and a text file you can safely copy. No example needs elevated privileges.
Warning: Do not reach for sudo just because a file is inconveniently located. Make a working copy in a directory you own first.
Confirm which executable will run and record its version. This is read-only:
$ command -v ed
/usr/bin/ed
$ ed --version
GNU ed 1.20.1
$ dpkg-query -W -f='${Package} ${Version}\n' ed
ed 1.20.1-1
There is no visible cursor. ed works with whole lines and ranges: in command mode you give an address or range followed by one command character. The buffer is a copy of the file until you explicitly write it, so opening a file does not modify it.
Checkpoint: If command -v ed prints nothing, stop and install the package through your normal system administration process. Do not paste an edit script into a different program and assume its address rules match.
Choose a real input path and copy it before experimenting. Replace the placeholder, and keep the original untouched:
$ cp --preserve=all /path/to/notes.txt /path/to/notes.txt.before-ed
$ cp --preserve=all /path/to/notes.txt /tmp/notes-ed.txt
$ test -r /tmp/notes-ed.txt && echo 'working copy is readable'
working copy is readable
The backup and working copy are ordinary files. If the source is sensitive, keep both in a directory with suitable permissions, and remove neither until the result has been checked.
Tip: The example uses /tmp only to make the disposable target obvious. On a multi-user system, choose a private directory if the contents are confidential.
Open the copy and print the whole buffer with line numbers. The ,n command means the range from the first line to the last, followed by the numbered-print suffix:
$ ed -p ': ' /tmp/notes-ed.txt
512
: ,n
1 Project notes
2 owner: alex
3 status: draft
4 review: Friday
:
The byte count is printed when the file is read, and your lines and count will differ. The colon is only a prompt chosen with -p, not part of the editor command.
The current address is usually the last line affected by the previous command. That is why explicit addresses make examples and scripts easier to review. The address forms:
.: the current line.$: the last line.1,8n: print a smaller slice of a large file./^status:/: the next matching line from the current position.Tip: If you do not know the line number, search first and print the result before changing it.
Use the s command to substitute text on an addressed line. This example changes the first matching status line and then prints that line:
: /^status:/s/draft/ready/p
status: ready
:
The address selects the line, the command is s/old/new/, and the final p prints the affected line. The basic regular expression is delimited by slashes, so a slash inside either value needs another delimiter or escaping.
By default, substitution changes the first match on each addressed line. Add g before the print suffix when every match on the line should change, for example 1,$s/[[:space:]]\+$//gp to remove trailing spaces and print changed lines.
Got it wrong? Use u immediately, then print the line again:
: u
: /^status:/p
status: draft
:
Checkpoint: An address error or a substitution that finds no match is a failed editor command. Read the diagnostic, inspect the addressed line, and do not continue a multi-command script until the assumption is correct.
Printing the buffer does not save it. The w command writes the buffer to the editor's current filename. Use f to display that filename first if you are unsure:
: f
/tmp/notes-ed.txt
: 1,$n
1 Project notes
2 owner: alex
3 status: ready
4 review: Friday
: w
512
: q
Warning: w changes the target file, and writing again can overwrite a useful result. Keep the backup until you have compared the files. If you quit with unsaved changes, GNU ed refuses with a warning; use Q only when you intentionally want to discard the buffer. There is no undo once the changed file has been overwritten, apart from restoring your backup or another copy.
Verify outside ed, without needing root:
$ grep -n '^status:' /tmp/notes-ed.txt
3:status: ready
$ diff -u /path/to/notes.txt /tmp/notes-ed.txt
@@
-status: draft
+status: ready
The diff header and line context vary with the input. If the output is broader than expected, do not replace the original.
Recovery: Restore the working copy from notes.txt.before-ed only after confirming that it is the backup you meant to use.
For a repeatable change, feed commands on standard input and write only after the substitution succeeds. This example edits a second disposable copy and ends with w and q:
$ cp --preserve=all /path/to/notes.txt /tmp/notes-scripted.txt
$ printf '%s\n' '/^status:/s/draft/ready/' 'w' 'q' | ed -s /tmp/notes-scripted.txt
$ grep -n '^status:' /tmp/notes-scripted.txt
3:status: ready
The -s option, also called script mode, suppresses byte counts and the exclamation prompt. Keep one command per input line unless the command explicitly accepts more lines. Test a script against a copy first.
If an edit must be applied to an important file, write to a new pathname and compare it before an explicit rename. Shell redirection or w is not a transaction.
-E for extended regular expressions. Leave it out when basic expressions are enough, because changing the dialect can make an existing script mean something different.-l when a non-match is expected. Treating every failed command as success can hide a typo, so check the exit status in automation and stop on unexpected errors.red is the restricted form of the same editor. It can edit files only in the current directory and cannot execute shell commands. That is useful at a boundary, but it does not replace file permissions or a general security policy. Confirm the executable and working directory before handing it an untrusted editing session:
$ command -v red
/usr/bin/red
$ mkdir -p /tmp/ed-sandbox
$ cp --preserve=all /path/to/notes.txt /tmp/ed-sandbox/notes.txt
$ (cd /tmp/ed-sandbox && printf '%s\n' '1p' 'q' | red -s notes.txt)
Project notes
Warning: Do not test restricted behaviour by pointing at a production directory. The current-directory rule is about where red may edit; the file's ownership and mode still decide whether the process can write it.
w.Q discards unsaved changes.