ex Command Reference

This reference is for the ex of ex-vi as seen on OpenBSD 7.7, or so. As such, the documentation may not be portable to other flavors of vi, such as vim, or even future versions of vi. The source file ex/ex_cmd.c is relevant as it has a table of the commands and some documentation on what the various options and flags mean. The order of commands is also important as the first matching entry for abbreviated inputs wins. That is, "d" means delete and not display. The vi(1) man page has an "EX COMMANDS" section though examples may be lacking.

An "ex command" means something entered in ex mode. ex mode can be accessed by launching ex instead of vi (this is the same inode as vi, just with a different name), or by pressing Q whilst in visual mode, or for a one-and-done ex command by pressing : in visual mode. The ":" method is likely how most people interact with ex these days. ex mode is easier to show examples for than visual mode:

The "|" in the context of an ex command is a command separator, or sometimes an argument separator. The default command is usually to print a line. Extra ":" are stripped from ex commands, so something like

is a valid ex command. The portability of ex commands may sometimes vary, so if curious test things in the OpenBSD base vi and also nvi and vim from ports: what does the :|:|: ex command do, assuming there are sufficient lines in the buffer?

The ex command syntax is highly irregular and the parser may be a giant hairball of code. With this in mind,

<end-of-file>

This obviously means to press control+d. Want a quick way to see the next few lines of the file? Press control+d, unless you are already at the end of the file.

For those not on a BSD system, seq(1) may be more typical than jot(1).

!

Executes a shell command, or filters lines through a shell command. That ex(1) supports filters is a huge step up from ed(1).

Shell command

Without a range given, a shell command is run. This could be for informational purposes, such as to show the date or current working directory, or you could go and read your mail while vi waits in the background. These days tmux(1) and other methods are used to run multiple terminal sessions, while in the olden times you may have had only a single modem line and no means for terminal multiplexing. Thus in the distant past there was a greater need to be able to run other programs from within the editor, and less so these days.

An unescaped "%" is replaced with the filename, so be sure to escape that if necessary. "!" and "#" are also special; "!" is replaced with the previous command, if any, and "#" the alternate filename, as made available by the "edit" and perhaps other commands.

The shellmeta option may also be relevant.

Filter

If "!" is given a range of lines a filter is run with the lines passed as standard input to the shell command.

#

The hash is a short-hand for the "number" command that shows the line numbers for the current or given range of lines. The "l" flag can be supplied to emit the lines also in literal form, e.g. "%#l" to number all lines in literal form. "%" must be escaped to "%%" for printf(1) in the example below.

If your vi prints an extra line for the above command, it has an unpatched autoprint bug, one where E_CLRFLAG is not being applied.

&

"&" repeats the last substitution, by default on the current line, though "%&c" would repeat the substitution on all lines: "%" is the range for all lines, and "c" the flag for confirmation. A "g" flag can also be provided that operates the same as the "g" flag to the s/// command.

@ *

Either @ or * execute a buffer, that is, the hopefully valid ex or vi commands in the given buffer are acted on. This can be used as a macro to run complicated commands. "@@" or "**" execute the last buffer executed from.

As an example, one may be working through a large diff and saving portions of the diff to be applied elsewhere. A relevant command might be ":1,write >> patch^Md1G" which appends to the "patch" file every line from the first line to the cursor position, then deletes to and including the current line. With the command in the buffer "b" one can then position the cursor on the end of a diff and enter "@b" to append that diff to the "patch" file and delete it from the file being worked through. Other portions of a large diff would simply be deleted as they are not relevant. This is how I sync my vi port with changes to the OpenBSD base vi. Other buffers could include searches for typical features of a large diff: lines beginning with "---", etc.

The ^M above must be a literal. This can be entered by pressing control+v (literal next) and then hitting return. Another way to include a newline is to yank a full line into a buffer, either with the vi input "byy or "by$ or with the ex command ". yank b". This assumes the line ends with a newline, which lines on unix really should.

Use the "display buffer" ex command to see what is in each buffer. It may also help to establish mnemonics for what buffers and used for what.

This ex command takes a range, so "1,10@@" would (try to) run whatever is in the last executed buffer on lines 1 through 10 inclusive. The command need not actually do anything to those specific lines, as the 1,10 could also be used as a loop counter, assuming there are sufficient lines in the file. However, this feature may be buggy in the base OpenBSD vi as of late 2025:

https://marc.info/?l=openbsd-tech&m=175719332024459&w=2

execute-buffer.patch

Another tip would be to put the ex command into the file, and include a command to copy the command to a buffer. This way, with the cursor on the line of the ex command, one can simply use "@@" which will yank the line into the previous buffer used then run whatever ex command(s) are desired.

This is a good way to quickly test out changes to a complicated ex command. Something like "byy@b will need to be used the first time to seed the b buffer with the command.

<

Shifts the given lines to the left, or lowers the indentation level. The number of < provided increase the shift amount, so "<" shifts by one level, "<<" by two levels, etc.

By the way, line ranges can be adjusted with + or - or +1 or -1 or ++ or -- or +2 or -2 and so forth. This allows one to search for, say, curly braces but only operate within those braces but not including them.

With the cursor on a line inside the above curly braces, the cryptic ?{?+,/}/-< ex command means to search backwards for a "{" (plus one line) and forwards for "}" (minus one line), producing a range of lines inside the curly braces that are indented one level to the left. In vi mode, "." will repeat the last command so could be used to repeat a shift command.

The expandtab and shiftwidth options may be relevant here.

=

Displays the line number of the last line of the file. Given a range, shows the line number of the last line of that range, so ".=" is an ex command to show the current line number the cursor is on.

>

Same as < but shifts the given range to the right by a level that depends on the number of > given. This increases the indentation level.

append

Append the input text after the specified line. If the user has terminal control the lines can be typed in followed by a final line of just ".". Input can be included following a "|", so "$append|foo" would append "foo" to the end of the file, plus any more lines supplied. The more complicated "global /^/ 0 append |blah" would append "blah" after the 0th line (to the first line, in other words) for as many lines as there are in the file. Under the global command additional lines are not prompted for.

g/^/0a|blah would be a quicker to type but less legible way to say the same thing.

The flag "append!" will disable autoindent, in the event your additions have stair-stepped across the screen incorrectly.

args

Shows the files in the arguments list. This does not include the "alternate file" you can bounce to with the "edit" command, but does include the files specified when the editor was launched, or the list can be rewritten by providing filenames to the "next" command.

change

Similar to the append command but instead changes or rewrites the range of lines given. There are some differences from append as "0 append" is valid but "0 change" is not. That is, append can target virtual lines such as 0 or the last ($) line of an empty file while change cannot.

You will need to enter . on a line by itself to end the change, unless a change is being done within a global command such as "global /foo/ change|bar".

The flag "change!" disables autoindent.

cd or chdir

Changes the current working directory of the process, e.g. "cd /etc". This may fail if the current file is modified and the file has a relative path; use the force flag to make chdir ignore this check, e.g. "cd! /etc". The working directory is inherited by child processes so the ex "!pwd" command could be used to show the current working directory.

copy or t

Copies the given range of lines to a target line. If no range is given the current line is used. A target line must be supplied. Some examples:

Where the cursor ends up after a command can be important for subsequent operations, hence the example of "|+" to reposition the cursor to the next line.

"t" for copy is inherited from ed(1) which only supports single letter command names. "c" in ed (and also vi) is instead the "change" command.

In theory the copy command takes the flags - + ^ # l p but I am unsure what if anything these do.

delete

Deletes a range of lines. An optional buffer may be provided to put the deleted lines into. I am unsure what the count and flags options do. In ex-vi (but probably not vim?) an upper case buffer letter will append to the given buffer instead of replacing the contents with the last deleted line. This can be used with the global command to accumulate deleted lines to the given buffer.

display

Displays various buffers, tags, screens, or maybe other features. "di b" is a shorter way to type "display buffers", and can be used to review what is in the buffers before using @ or * to execute a buffer.

edit

Edit a different file. A +cmd option can be provided to execute a command when the file is loaded, similar to the -c flag when starting vi. % and # are expanded as the current and alternate filename, if available.

If your vi supports capital letters (mine does not, as I often drag on the shift key due to pressing :, and never want :Edit), Edit will open the file in a new screen.

In visual mode, control+shift+6 will flip to the alternate file, same as the e# ex command, though I've mapped that to \e to be easier to type. This makes it easy to move between a pair of files.

exusage

Displays the usage for an ex command.

file

Display or optionally change the file name. The display is the same as the control+g key in visual mode. The "change the file name" feature points the current buffer at some other filename; assuming a prior `vi foo` the ex command

will point the foo file buffer to bar and write the buffer to bar. This is something like the `cp foo bar` shell command.

global or v

Runs commands for each line that do match a pattern. "global!" or "v" do the same, except on lines that do not match the pattern. The "v" form is where the `grep -v` flag came from. global operates on all lines by default, though may be restricted to a given range of lines.

Using the global command as a loop is something of a hack, and assumes there are sufficient lines in the file for the loop to complete. Loops can also be used with s/// to do complicated things, such as to append one "x" to the first line of the file, two "x" to the second line of the file, etc.

The global command may run into problems if the lines modified appear after the current line being worked on, or if the number of lines in the file shrinks. Hence the use of "copy -" to duplicate the lines in the file (copy to previous line) instead of something like "copy .".

An advanced command that may cause problems if you forget it is running and try to run other ex commands is to, from ex mode (press Q to get there if in visual mode), to run visual mode for each line matching a given pattern:

A use for this is to review matching lines in a file or within a range of lines, for example to check uses of a variable only within a particular function. Assuming a language with {} braces,

will bounce to the ending brace, set the "m" mark, then return to the starting line. Then one can run, again starting from ex mode (again press Q to get there),

to search for "ep->db" uses from the current line to the mark m, and not elsewhere within the file. Press Q to move to the next match. The search is done when the editor returns to ex mode.

help

Shows some documentation.

insert

Inserts lines before the specified line. Uses the same code as the append and change ex commands, so "insert!" disabled autoindent. Oddly enough one can insert "before" the fake zeroth line of the file, which technically would mean to insert to the -1 line of the file (??) but it works and inserts to the beginning of the file.

If given a range of lines the insert happens before the last line of the range. "%insert|foo" is thus equivalent to "$insert|foo".

Press . on a line by itself to complete inserting lines, unless the insert is running under a global command.

join

Joins a range of lines together. This by default adds a space between each line (and maybe a double-space should a line end with a period, but I removed that feature from my vi). Being good with ranges (which could also include user-specified marks) may allow one to get more out of this command.

The "join count" form should probably not be combined with the "range join" form, or at least I cannot intuit how combinations of the two will behave. Maybe you can make it work, or go and see what exactly the code does?

When in ex mode (press Q to get there from visual mode) the # or l flags can be used to affect the autoprint:

list

Print lines showing tabs (control+i) and where the end-of-line is (with a $):

Another way to view tricky characters is to filter the buffer through a hex viewer (hence vim having xxd and xxd -r), or to run the "!hexdump -C %" ex command to inspect the current file with hexdump(1) or od(1) or whatever.

"list #" or with less typing "l#" will include the line numbers in the output. list takes a count but I have no idea how to make it do anything useful.

mark or k

Marks a line with the given character. Useful for jumping back to a mark, or for use in a line range for some other ex command. "Jumping to a mark" might also be done using ctags(1), or one could use both ctags for more permanent marks and marks for temporary locations, such as "mark f" for where the footnotes are in a file. One probably should develop a mnemonic for marks so that remembering what letter is used for what is easier.

The alias "k" is from ed(1) which only supports single character command names, and "m" is short for move.

A range will mark the last line of the range, so "%mark a" and "$mark a" do the same thing, put the mark a at the end of the file.

In ex mode a mark can be moved to with the command 'a (short for the range ,'a or 'a,'a) which is less typing than "'a print", print generally being the default command. In visual mode 'a or `a can be used to move to a mark, though the first of these is not a range expression like it is in ex mode.

move

Move the given range of lines after the target line. The default range is the current line, and a target line must be supplied. A mark may be necessary to get back to near where you came from, as move places the cursor somewhere in the target area.

mkexrc

Writes various settings to the specified file; these can be loaded using the "source" command or by saving to ~/.exrc or some other standard init file.

next

Edit the next file in the arguments list; the "args" command will show this list. Of course it is more complicated than just moving to the next file.

The "maybe" part is due to next failing if there are unwritten changes or whatnot. My version of vi sets autowrite and is better about switching files with minimal message spam. In the olden days with less version control and backups autowrite was perhaps a more dangerous operation.

"Next" or "N" will open the next file in a new screen, unless you prevented your vi fork from doing that because you too often drag on the shift key when typing : and enter :N by mistake.

preserve

Save the file for recovery with the the "ex -r" option. Given version control, backups, and that vi is less crash prone than it once was this is probably a relic.

previous

Edit the previous file in the arguments list; the "args" command will show this list. Simpler than the "next" command in that "previous!" to force the change is the only complication.

print

Prints the specified lines. This is generally the default command if one is not provided, such as when only a line number or mark is provided as the range. Note that printing a range of lines will move the cursor to the last line of the range.

put

Puts the contents of the named buffer at the given line, the current line by default. Use the yank command to get lines into a buffer, or by other means in visual mode, such as 1G"k2yy to yank the first two lines of the file into the buffer k.

put is probably more sensible than copy if the lines will be duplicated at least two times, though vi does not shy away from TIMTOWTDI. yank and put can also be used to move lines between different files, as the copy and move commands only work within the given buffer.

quit

I hear that exiting the editor is a foreign concept to some emacs users.

read

Read a file into the current buffer, to the current line by default. For example the % filename could be used to read a file into itself, thus duplicating all the lines. (% is also a range operator for "all lines in the file", so context very much matters in vi.)

Of course there are complications.

For the last command use control+d (EOF) to exit from cat(1); the command is otherwise similar to the "append" command, only more complicated.

recover

Recover the given filename previously preserved, presumably similar to the -r option but I hardly ever use this feature.

rewind

Rewind the argument list, or in other words go back to the first file. Use "args" to show the argument list, or change that list using suitable arguments to the "next" command. Add a bang to force matters.

set

Display or set editor options. The "mkexrc" command can save the options and more to a file.

shell

Runs your shell (whatever is in the SHELL environment variable) under vi.

source

Executes ex commands from a file, maybe saved by the "mkexrc" command.

substitute or s///

Replace matched text with other text. Some of the concepts here have leaked into other languages, such as using \U to upper case the replacement text, though I am unsure of the exact history on who copied from whom.

I mostly use s/// in vi for simple things as I'm more prone to use Perl if a complicated replacement is necessary.

See also the "&" and "~" commands.

suspend

Puts vi into the background. "suspend" is more typing than "su" or the even shorter control+z, assuming "dsusp" hasn't been mapped to some other key with stty(1).

tag

Edit the file containing the specified tag. Add a bang to help force matters, and "Tag" instead of "tag" will open a new screen, unless your version of vi disables that for reasons covered above. Assumes ctags(1) has been setup, and there are various options related to tags.

tagnext

tagpop

tagprev

tagtop

Tag context or stack related commands I am not much familiar with as I mostly do not use tags.

undo

Undo the last change made to the file.

version

Display the version of ex-vi. This may not be maintained in some versions of vi.

visual

Enter visual mode from ex mode. There are complications such as using ^ - . + to influence the screen position, or a range can be provided to position the cursor starting on a particular line. Maybe you can find a use for these options?

viusage

Displays usage for a vi command.

write

Writes the entire file unless a range is given, among other alternatives.

xit

yank

Copy lines to the named buffer. See also the "put" command.

z

Adjusts the window size. Probably was useful back in the day when the modem was too slow to refresh a full 80x24 in a timely fashion.

~

Substitute using the last regular expression and last substitute replacement pattern. See also s/// and the "&" command.