Skip to content

Latest commit

 

History

History
812 lines (636 loc) · 26.7 KB

README.md

File metadata and controls

812 lines (636 loc) · 26.7 KB

Build Status

The standard Unix text editor

Description

Ed is an implementation of the Unix line editor. It is 100% POSIX compatible, 8-bit clean with 64-bit addressing. It includes the GNU regular expression library, but can be linked against any POSIX-compatilbe alternative.

Several optional extensions to the SUSv4 standard are described below. The extensions are careful not to alter ed's standard behavior and so can be safely enabled by default.

Installation

Binary Distributions

Some binary packages are available - see Releases.

Prerequisites for building from source

To build ed from source, the following prerequisite packages are needed:

  • GNU autoconf,
  • GNU automake,
  • GNU autopoint,
  • GNU gettext,
  • GNU libtool, and
  • GNU texinfo.

Additional packages for generating PDFs of Brian W. Kernighan's ed tutorials are:

  • GNU texi2dvi,
  • GNU roff, and
  • ghostscript.

CentOS/RHEL

On Red Hat and Red Hat-based systems, the prerequisite packages can be installed by running the commands:

sudo dnf group install 'Development Tools'
sudo dnf install -y gettext-devel ghostscript groff \
    openssl-devel textinfo

Debian/Ubuntu

On Debian/Ubuntu systems, the prerequisite packages can be installed by running the command:

sudo apt install -y build-essential autoconf automake \
    autopoint gettext ghostscript groff libssl-dev \
    libtool texlive texinfo

Fedora

On Fedora, the prerequisite packages can be installed by running the commands:

sudo dnf group install 'C Development Tools and Libraries'
sudo dnf install -y gettext-devel ghostscript groff \
    openssl-devel texinfo-tex textinfo

OpenSUSE

On OpenSUSE, the prerequisite packages can be installed by running the commands:

sudo zypper --non-interactive install -t pattern devel_C_C++
sudo zypper --non-interactive install -y gettext-tools ghostscript \
    groff libopenssl-3-devel textinfo

Building from source

The easiest way to build from source is to run:

curl -L https://github.com/slewsys/ed/releases/download/v2.0.13/ed-2.0.13.tar.gz |
    gzip -cd |
    tar -xf -
cd ./ed-2.0.13
./configure --enable-all-extensions --with-included-regex
make
sudo make install

Building from Git

Updating Natural Language translation files requires:

  • GNU gettext tools.

Generating documentation requires:

  • a typesetting system (e.g., groff or troff),
  • GNU texinfo and
  • additional tools for producing PDFs (.e.g, texi2pdf and ps2pdf).

Ruunning tests requires:

  • GNU make
  • GNU automake,
  • GNU autoconf and
  • GNU libtool.

Assuming these are available, run:

git clone https://github.com/slewsys/ed
cd ./ed
./autogen.sh
./configure --enable-all-extensions --with-included-regex
make
make check
sudo make install

Building a Debian package

To build a Debian package with gbp:

Install prerequisites on Debian/Ubuntu:

sudo apt build-dep ed
sudo apt install git-buildpackage libssl-dev texinfo

Create a destination directory for Debian build products:

mkdir build
cd ./build

Clone ed repository into destination directory and run Git Buildpackage (gbp):

git clone https://github.com/slewsys/ed ed-2.0.13
cd ./ed-2.0.13
git branch upstream
gbp buildpackage --git-debian-branch=main --git-upstream-tree=branch

gbp will fail with the error:

dpkg-source: info: local changes detected, the modified files are: ed-2.0.13/Makefile.in ed-2.0.13/aclocal.m4 ed-2.0.13/config.h.in ed-2.0.13/configure ed-2.0.13/doc/Makefile.in ed-2.0.13/doc/bwk/Makefile.in ed-2.0.13/lib/Makefile.in ed-2.0.13/src/Makefile.in ed-2.0.13/testsuite/Makefile.in

This reflects the fact that the ed repository does not contain generated files. To resolve this, add the missing files to the tar archive and run gbp again:

cd ..
gunzip ./ed_2.0.13.orig.tar.gz
tar --append -f ./ed_2.0.13.orig.tar \
ed-2.0.13/{Makefile.in,aclocal.m4,config.h.in,configure,doc/Makefile.in,\
doc/bwk/Makefile.in,lib/Makefile.in,src/Makefile.in,testsuite/Makefile.in,\
po/stamp-po}
gzip ed_2.0.13.orig.tar
cd -
gbp buildpackage --git-debian-branch=main --git-upstream-tree=branch

The build products, Debian packages with deb suffix, should appear in the parent folder (build).

Tutorials

Brian W. Kernighan's ed tutorials are included as PDFs, info documents and NROFF manuscripts. See doc/bwk/ or, from within ed, type:

!info ed RET m tutorial RET

Extensions to the SUSv4 standard

This implementation of ed scores 100% on The Open Group Shell and Utilities Verification Suite of IEEE Std 1003.1-2017 when either the environment variable POSIXLY_CORRECT is defined or ed is invoked with commnad-line option -G.

None of the ed extensions discussed below are enabled by default. They can all be enabled with configure option --enable-all-extensions. Alternatively, individual extensions can be enabled as described below.

Command-line address arguments

Command-line address arguments are enabled with the configure option --enable-address-arguments. Valid address arguments are of the form:

Command-line Argument Action
+N Set the current line (dot) to line number N.
+/RE Set dot to next line matching regular expression RE.
+?RE Set dot to the previous line matching regular expression RE.

Address arguments can be combined, e.g.,

ed +3 +/RE1 +?RE2 FILE

searches FILE forward from line 3 for RE1 and, from there, backward for RE2.

Scrolling

Extended scrolling capabilities are enabled with the configure option --enable-ed-scroll. These are summarized as follows:

 (.)zn - Displays next n lines from given address
 (.)Zn - Displays previous n lines from given address
 (.)]n - Displays next n / 2 lines
 (.)[n - Displays previous n / 2 lines

where n is a number which, if not specified, defaults to the current window height. Half-page scrolling is presently limited to line-oriented documents - i.e., those whose lines are shorter than the window width.

The environment variables COLUMNS and LINES are used, if available, to set the default window dimensions.

UTF-8 multibyte characters with East Asian Ambiguous width attribute are displayed as narrow (i.e., occupying a single column) per recommendation of Unicode technical report UAX #11.

Cut-and-Paste

Cut-and-paste is enabled by configure option --enable-ed-register.

It is implemented by means of ed's move (m), copy (t) and delete (d) commands:

 (.,.)m>  - Moves address range to unnamed register (overwriting
            any previous contents).
 (.,.)t>  - Copies address range to unnamed register (overwriting
            any previous contents).
 <m(.)    - Moves unnamed register contents to after given address.
 <t(.)    - Copies unnamed register contents to after given address.

Comparing the syntax of cut-and-paste commands with ed's move and copy commands:

 (.,.)m(.) - Moves address range to after given address.
 (.,.)t(.) - Copies address range to after given address.

evidently the redirection operator < reads from the unnamed register and > writes to the unnamed register.

Deleted lines are automatically moved to - and overwrite the contents of - the unnamed register. So, for example, after deleting lines 1 through 10, they can be restored from the unnamed register to the end of buffer via the ed command sequence:

 1,10d
 <m$

Named registers are also supported: <n reads from register n, where n is an integer in the range [0 ... 9], and >n writes to register n.

Lines can be appended to registers using the syntax >>n or >>, in the case of the unnamed register.

Finally, it's possible to move and copy the contents of registers directly to other registers. For instance, to expand on the example above, after deleting lines 1 through 10, let's move them from the unnamed register to register 5 and then later restore the lines from register 5 to the end of the buffer as follows:

1,10d
<m>5
...
<5m$

File Globbing

File globbing is enabled by the configure option --enable-file-globbing. This doesn't turn ed into a multi-file editor, but it allows ed to be invoked with multiple file arguments which are maintained in a list and introduces variants of existing commands accepting glob(3) file patterns. File globs are constructed with the following symbols:

*      - matches any part of file name, except for a leading
         dot (**.**).
?      - matches any single character in a file name, except for
         a leading dot (**.**).
[...]  - matches any character in the string represented by the
         ellipsis (**...**).
[!...] or [^...]
       - matches any character not in the string represented by
         the ellipsis (**...**).
[x-y]  - matches any character in the range bounded by characters
         x and y.
[^x-y] - matches any character not in the range bounded by
         characters x and y.
~/     - expands to the current user's home directory, but only
         if used as a prefix.

For example, the file glob ~/*.txt expands to files in the current user's home directory with suffix .txt.

The new commands are summarized as follows:

~e file-glob [...]
           - Sets the file list to the expansion of file-glob and
             the default file to the first in the list, then
             edits that file and prints its name to standard
             output. Any previous file list and/or buffer contents
             are discarded.

             If file-glob is not specified, then sets the default
             file to the first in the current file list and edits
             that file.
~E file-glob [...]
           - Unconditionally edits the first file in the file
             list. Similar the **~e** command except that
             unwritten changes are discarded without warning.
~en        - Edits the "next" file in the file list and prints
             its name to standard output. Any previous buffer
             contents are discarded.
~ep        - Edits the file that comes "previous" in the file
             list  and prints its name to standard output. Any
             previous buffer contents are discarded.
($)~r file-glob
           - Reads the file (uniquely) matching file-glob to after
             the addressed line. If file-glob is not specified,
             then the default file is used.
(1,$)~w file-glob
           - Writes the addressed lines to the file (uniquely)
             matching file-glob. If file-glob is not specified,
             then the default file is used. The default file name
             is unchanged.
(1,$)-W file-glob
           - Appends the addressed lines to the file (uniquely)
             matching file-glob. If file-glob is not specified,
             then the default file is used. The default file name
             is unchanged.
(1,$)~wn   - Writes the addressed lines to the default file, then
             edits the file that comes "next" in the file list.
(1,$)~wp   - Writes the addressed lines to the default file, then
             edits the file that comes "previous" in the file list.
~f file-glob [...]
           - Sets the file list to the expansion of file-glob,
             sets the default file to the first file in list, and
             prints the file list to standard output. The editor
             buffer is unchanged.
~fn        - Sets the default file name to the "next" in the file
             list and prints the name to standard output. The
             contents of the editor buffer are unchanged.
~fn        - Sets the default file name to the "previous" in the
             file list and prints the name to standard output.
             The contents of the editor buffer are unchanged.

When a file-glob is used in the above commands, if no files match, then the unexpanded file-glob is used instead.

For the read (~r) and write (~w) variants to succeed, file-glob must expand to at most one file. Otherwise, these fail with diagnostic Too many file names.

If the first character of a file argument is exclamation mark (!), then the rest of the line is interpreted as a shell command. In this case, backslash (\) escape processing is limited to protecting percent signs (%) from being expanded to the default file name.

In contrast, when opening a file, backslash escape processing is limited to protecting an initial exclamation mark (!), e.g., ed \!date opens the file named !date, whereas ed '!date' reads the output of the Unix date command into the editor buffer. A more portable way of opening a file whose name begins with an exclamation marks is to specify all or part of the pathname, e.g. ed ./'!date', or within ed: ~e ./!date.

External Filtering

The ability to filter lines through an external filter is enabled with the configure option --enable-external-filtering. This is summarized as follows:

 (n, m)!shell-command

where lines n through m are written to the standard input of any Unix command, shell-command, whose standard output replaces the range of lines in the editor buffer. Unlike most other ed commands, at least one address must be specified. Otherwise a shell command is run, but no filtering is done.

For example, to use the Unix transpose utility, tr, to convert a line to uppercase:

$ ed -p '*'
*a
hello, world
.
*.! tr a-z A-Z
13
*p
HELLO, WORLD

File Locking

File locking is enabled by configure option --enable-file-lock. Advisory locking is provided by flock (2), if available, otherwise fcntl (2). If help mode is enabled (i.e., if either ed is invoked with command-line option -v or, within ed, command H is issued), then reading or writing a locked file prints a diagnostic to standard error. For historical compatibility, no errors are flagged.

Macros

Macros are collections of ed scripts stored in registers that can be run against the editor buffer using the syntax: @n where n is a register number. Macros are enabled with the configure option --enable-ed-macro.

Here's an example session that loads and executes an ed script:

$ ed -p '*'          <-- Prompt for commands with '*'.
*r contrib/cats.ed   <-- Read a script from the ./contrib directory.
683
*,m>1                <-- Move it to register 1.
*r COPYING           <-- Read in another file with lots of blank lines.
55935
*@1                  <-- Run the script in register 1.
...
*wq COPYING          <-- A wq command alone would overwrite cats.ed!
55932                <-- Saved file is smaller by three newlines.
$

Script Flags

ed dropped the programming constructs of its ancestor, QED, that were later adopted by sed, but it's REPL interface and random access addressing still prove useful on occasion. Additional command-line flags for scripting are enabled by configure option --enable-script-flags. These are summarized as follows:

-i, --in-place[=SUFFIX]  Write file before closing, optionally
                         back up the original.
-e, -expression=COMMAND  Add COMMAND to script input - implies -s.
-f, --file=SCRIPT        Read commands from file SCRIPT - implies -s.

The flag -f enables stand-alone ed scripts. For example:

#!/bin/ed -f
#
# @(#) cats.ed
#
# SYNOPSIS
#   cats.ed file >new
#
# DESCRIPTION
#   This script replaces a sequence of multiple newlines in a file with
#   a single newline and prints the result to the standard output.
#
#
# Append token (∴@∴) to end of each line.
,s/$/∴@∴/
# Join all lines
,j
# Substitue two newlines for sequences of multiple tokens not at EOF.
s;\(∴@∴\)\{2,\}\([^∴]\);\
\
\2;g
# Substitue one newline for sequences of one or more tokens.
,s;\(∴@∴\)\{1,\};\
;g
# Print the result to standard output.
,p
# Avoid buffer-modified warning by quitting unconditionally.
Q

Flags -i and -e are also borrowed from sed. The sed command:

sed -i -e 's/old/new/' file

in ed dialect becomes:

ed -i -e ',s/old/new/' file

Note the difference here: sed commands are applied to every input line by default, whereas ed requires an explicit range.

Each ed expression argument is placed on a line by itself. So the ed script:

a
hello
world
.
g/x*/s//!/gp

could be written on the command line as:

ed -e 'a' -e 'hello' -e 'world' -e '.' -e 'g/x*/s//!/gp'

or, using Bash shell construct $'string' to decode backslash-escaped characters in string:

ed -e $'a\nhello\nworld\n.\ng/x*/s//!/gp'

Note that this last example is equivalent to the more traditional (and equally unreadable):

printf 'a\nhello\nworld\n.\ng/x*/s//!/gp\n' | ed -

ED Environment Variable

The ed environment variable, ED, is enabled with the configure option --enable-ed-envar. Command-line options can then be enabled automatically. For example, adding a line to one's shell profile such as:

export ED='-vp *'

provides ed with a command prompt (*) and enables help mode.

Binary Files

When a file containing at least one ASCII NUL character is written, a newline is not appended if it did not already contain one upon reading. In particular, reading /dev/null prior to writing prevents appending a newline to a binary file since /dev/null contains no newline.

For example, to create a file with ed containing a single NUL character:

$ ed -p '*'
*a
^@
.
*r /dev/null
0
*wq junk
1
$

Similarly, to remove a newline from the end of a 1k binary file bin:

$ ed -p '*' bin
1024
*r /dev/null
*wq
1023
$

BSD Dialect

BSD dialect has been implemented wherever it does not conflict with the SUSv4 standard. This includes the following commands:

(.,.)s[rgpn]     - to repeat a previous substitution,
(1,$)W           - for appending text to an existing file,
(1,$)wq          - for exiting after a write, and
(.)z[n]          - for scrolling through the buffer.

BSD line-addressing syntax - i.e., ^ as synonym for + and % as synonym for 1,$.

Global Search

The SUSv4 interactive global commands G and V are extended to support multiple commands, including a, i and c. The command format is the same as for the global commands g and v, i.e., one command per line with each line, except for the last, ending in backslash (\).

Piped Input

For backward compatibility, errors in piped scripts do not force ed to exit. SUSv4 only specifies ed's response for input via regular files (including here documents) or standard input.

SunOS Dialect

For SunOS ed compatibility, ed runs in restricted mode if invoked as red. This limits editing of files in the local directory only and prohibits shell commands.

Deviations from the SUSv4 standard

Extended Regular Expressions

Extended regular expression syntax is available if ed is invoked with command-line flag -E or -r.

Pattern delimiters

To support the BSD s command (see Repeated Substitution Modifiers below), substitution patterns cannot be delimited by numbers or the characters r, g and p. In contrast, SUSv4 specifies that any character other than space or newline can used as a delimiter.

Undo within global command

Since the behavior of undo (u) within a global (g) command list is not specified by SUSv4, ed follows the behavior of the SunOS ed: undo forces a global command list to be executed only once, rather than for each line matching a global pattern. In addtion, each instance of u within a global command undoes all previous commands (including undo's) in the command list. Alternative approaches seem either too complicated to implement or too confusing to use.

Move within global command

The move (m) command within a global (g) command list also follows the SunOS ed implementation: any moved lines are removed from the global command's active list.

Shell command arguments

If ed is invoked with a name argument prefixed by exclamation mark (!), then the remainder of the argument is interpreted as a shell command. To protect the command from interpretation by the shell, it should be quoted. For example,

$ ed -p '*' '!echo "hello, world"'
12
*,p
hello world
*

In the previous example, note that the default file name is not set, i.e.,

*f
*

Examples

Repeated Substitution Modifiers

Sequence Effect Explanation
s;a;x Repeated substitution command (s)
s;b;y always repeats most recent substitution.
s s;b;y
Sequence Effect Explanation
s;a;x Intermediate search commands (/b)
/b do not affect regexp of repeated
s s;a;x substitution command (s).
Sequence Effect Explanation
s;a;x Repeated substitution with regexp modifier
/b (r) uses most recent regexp, i.e., of
sr s;b;x intermediate search (b).
Sequence Effect Explanation
/a Repeated substitution with regexp modifier
s;b;x s;b;x (r) uses most recent regexp, i.e., of
sr s;b;x last substitution (b).
Sequence Effect Explanation
s;a;x Repeated substitution with regexp modifier
s;b;% s;b;x (r) picks up regexp from last search
/c (c), not from repeated substitution
s s;b;x command (s). Effect of modifier preserved
sr s;c;x by subsequent repeated substitution.
s s;c;x
Sequence Effect Explanation
s;a;x;g Toggling effect of repeated substitution
s s;a;x;g modifier (g) on repeated substitution
sg s;a;x; command (s).
sg s;a;x;g
Sequence Effect Explanation
s;a;x;2 Repeated substitution with match selection
s s;a;x;2 modifier (3) overrides any previous
s3 s;a;x;3 match selection (2).
Sequence Effect Explanation
s;a;x;2g Repeated substitution with global and
sg s;a;x;2 match selection modifiers (g and 3)
sg s;a;x;2g operate independently of each other.
s3 s;a;x;3g NB: s;a;x;2g substitutes globally after
s4g s;a;x;4 the second match, whereas s;a;x;g2
sg s;a;x;4g substitutes every other match.
Sequence Effect Explanation
s;a;x;4g3 Repeated substitution with global modifier
sg s;a;x;4 (g) toggles a global modulus (g3),
sg s;a;x;4g3 but a new global modulus modifier (g2)
sg2 s;a;x;4g2 overrides the old (g3).
s3 s;a;x;3g2
Sequence Effect Explanation
s;a;x;gl Print suffix (l) is toggled by repeated
sp s;a;x;g substitution print modifier (p).
sp s;a;x;gl

References

The ed algorithm is described in Kernighan and Plauger's book Software Tools in Pascal, Addison-Wesley, 1981.

Brian W. Kernighan's ed tutorials are included courtesy of Lucent Laboratories.

Please submit issues or pull requests to: https://github.com/slewsys/ed