Edit Text Files Line by Line with GNU ed

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.

1. Check the installed editor

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.

2. Make a disposable working copy

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.

3. Open the copy and print it

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:

Tip: If you do not know the line number, search first and print the result before changing it.

4. Change one line and verify

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.

5. Save after checking the result

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.

6. Turn a known edit into a script

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.

7. Understand red before using it

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.

Done means