Man pages are often authored using the presentation-oriented man(7) roff macros. roff is a typesetting language ultimately descended from RUNOFF, the first computer typesetting system, developed for the CTSS project in 1963. (Refer to Appendix A for a brief historical timeline.)
In roff, formatting is carried out by the means of requests - formatting commands. To simplify repetitive formatting tasks, requests can be grouped into macros, each of which performs a particular function (e.g. formatting text as a heading). A collection of macros all serving a shared purpose - for example, formatting a letter - is called a macro package. man(7) is a macro package.
However, man(7) is not the only macro package available for formatting man pages. Man pages can also be authored with the semantics-oriented mdoc(7) macros. Man pages in the mdoc(7) format can be directly viewed on any OS which uses either the man-db
system or the mandoc
system. (For a list of OSes which can view mdoc(7) man pages "out of the box", refer to Appendix B.)
This document tries to give a sense of what it's like to write man pages using mdoc(7).
Unfortunately, the fact that the man(7) macros are presentation-oriented limits the usefulness of the man page system: presentational markup doesn't indicate the nature of what's being marked up. For example, the italicisation of text might indicate one of any number of things: that the text is a placeholder for an argument to a command or function, that the text is a program, that the text is in a natural language different from the rest of the text, or merely that the text is being emphasised to the reader. Further, there is not necessarily any consistency in these usages from one set of documentation to the next.
In contrast, the mdoc(7) macros are semantics-oriented, so that, for example, if one wishes to search for all man pages referencing the PATH
environment variable, one can do:
$ apropos Ev=PATH
where Ev
is the macro used to indicate text that's an environment variable.
Additionally, the fact that the mdoc(7) is semantically-oriented greatly facilitates accurate conversion to other formats, such as HTML, Markdown or PDF.
Just as in Web design, mdoc(7) separates content from presentation.
Markdown is a bad choice for producing quality man pages. It's presentation markup, not semantic markup, without even the possibility of the document author systematically adding semantics (unlike, for example, HTML, where one can add semantics via the class
attribute). It has many flavours, each slightly different from the others: for example, the pandoc(1) man page lists "commonmark", "commonmark_x", "markdown", "markdown_mdd", "markdown_phpextra" and "markdown_strict" as distinct formats. And although the CommonMark spec exists, to a certain extent it suffers from the "now you have 15 competing standards" problem. It's certainly relatively easy to write, but it's much less easy to parse, hindering format conversion.
For a more comprehensive critique of Markdown, refer to this post on undeadly.org.
Even if you refuse to write mdoc(7) when authoring man pages, please try to use a source language that substantially facilitates semantic markup (e.g. HTML).
The current de facto standard roff implementation is groff(1). (Historical implementations include nroff, troff and ditroff; refer to Appendix B for details). However, mandoc(1) implements a subset of roff relevant to the formatting of man pages. Both groff(1) and mandoc(1) support the mdoc(7) macro package in addition to man(7).
roff is a line-oriented language. Each line is either a control line, or a text line. Control lines have commands, such as a request or macro; text lines are plain text, with no requests/macros, but possibly some escape sequences (cf. below). A control line usually has an initial .
, followed by a request / macro. Lines that need to begin with a literal .
are preceded a zero-width space, \&
: \&.
. Both groff(1) and mandoc(1) accept the use of '
as a control character, but mandoc(1) treats it just like .
, whereas groff(1) treats it as a no-break control character; refer to the groff(7) man page for details. (groff(7) also notes "Employing the no-break control character to invoke requests that don't cause breaks is harmless but poor style.")
Escape sequences, which begin with a leading backslash (\
), can be used to produce particular glyphs, such as an em dash (\(em
-> —
), a check mark (\(OK
-> ✓
) or an accented character (\(:y
-> ÿ
); further details can be found in mandoc_char(7) or groff_char(7). Note that a literal backslash is produced by \e
, not \\
, and that a comment line is introduced by .\"
.
When writing man pages in mdoc(7), the mdoc(7) macros should be used as much as possible; using roff requests directly should only be a last resort. For example, the roff request .sp
requests vertical space / a blank line, but there is typically no need to use this, as mdoc(7) will usually provide a typographically appropriate amount of vertical space wherever required.
Firstly, the standard preamble:
.Dd January 1, 2020
.Dt MDOC-EXAMPLES 7
.Os
The Dd
macro is for the Document Date, i.e. the date the source was last edited. Use US-style date format: in strftime(3) terms, the format is %B%e, %Y
.
The Dt
macro is for the Document Title, i.e. the name of the software being documented, and its manual section.
The Os
macro produces the name of the Operating System the man page is being accessed on.
The preceding will produce a header like:
MDOC-EXAMPLES(7) General Commands Manual MDOC-EXAMPLES(7)
together with a footer like:
[OS details] January 1, 2020 [OS details]
where [OS details]
is a placeholder for the actual OS details that would be produced.
Next, the NAME section:
.Sh NAME
.Nm mdoc-examples
.Nd print example mdoc source to stdout
The Sh
macro is for a Section Header. In this instance, it's passed the argument NAME
; the argument of Sh
should be capitalised.
The Nm
macro is for both defining and producing the name of the software. In this instance, it's passed the argument mdoc-examples
, which defines what will be produced by Nm
on its own - cf. below.
The Nd
macro is for producing a short description of the software, as provided by its argument. The argument should not have any punctuation at the end.
The preceding will produce something like:
NAME
mdoc-examples — print example mdoc source to stdout
Next, the SYNOPSIS section:
.Sh SYNOPSIS
.Nm
.Op Fl v | Fl h
The use of Nm
without arguments will produce the name of the software as previously defined, i.e. "mdoc-examples".
The Op
macro is for producing text about an Optional argument, and the Fl
macro indicates a Flag. This line demonstrates parsed and callable macros: the Op
macro is a parsed macro, which means it gets parsed for callable macros. This is why we can use Fl
here: Fl
is a callable macro, which takes as its argument the literal for the flag. Details about the parsed and callable statuses of mdoc(7) macros can be found in the "MACRO SYNTAX" section of the mdoc(7) man page.
The preceding will produce something like:
SYNOPSIS
mdoc-example [-v | -h]
Next, the DESCRIPTION section:
.Sh DESCRIPTION
.Nm
prints some example
.Xr mdoc 7
source to stdout.
The example source is for illustrative purposes only.
The Xr
macro is for Cross References to other man pages. It takes two arguments: the name of the man page, and its section.
As a stylistic point, mandoc(1) prefers that a new sentence start on a new line, but this is not required.
The preceding will produce something like:
DESCRIPTION
mdoc-examples prints some example mdoc(7) source to stdout. The example source is for illustrative purposes only.
Next, the OPTIONS section:
.Sh OPTIONS
.Bl -tag -width x
.It Fl v
Print version information to stdout.
.It Fl h
Print usage information to stdout.
.El
This section documents the options mentioned in the SYNOPSIS section.
The Bl
macro Begins a List; the El
macro Ends the List. The -tag
argument specifies that item bodies should be indented based on the width of the value supplied to the -width
argument; in this case, the indentation will be the width of a single x
.
The It
macro is a list Item. It's a parsed macro, so we can use the Fl
macro in its argument, with a literal as the argument to the Fl
macro.
The preceding will produce something like:
OPTIONS
-v
Print version information to stdout.
-h
Print usage information to stdout.
Next, the EXIT STATUS section:
.Sh EXIT STATUS
If
.Nm
is able to print the examples, it exits with status code 0.
The preceding will produce something like:
EXIT STATUS
If mdoc-examples is able to print the examples, it exits with status code
0.
If appropriate, the Ex -std
macro can be used to provide boilerplate text about exit values, e.g. "The mdoc-examples utility exits 0 on success, and >0 if an error occurs."
Next, the SEE ALSO section:
.Sh SEE ALSO
.Xr mandoc 1 ,
.Xr mdoc 7 ,
.Xr roff 7
Man pages referenced in this section should be sorted by section number, then alphabetically within each section grouping, and the list of man pages should be separated by commas (,
). Note how the commas are a distinct final argument on the request lines; this allows the punctuation to be handled appropriately (i.e. being put immediately after the closing parentheses, without any space between them).
The preceding will produce something like:
SEE ALSO
mandoc(1), mdoc(7), roff(7)
Finally, the AUTHORS section:
.Sh AUTHORS
.An Alexis
The An
macro indicates an author, and is parsed for other macros, such as Mt
, MailTo, an email address.
The preceding will produce something like:
AUTHORS
Alexis
The complete mdoc-examples.7 file can be viewed directly with man(1) on any mandoc
-based or man-db
-based system, without any need for compilation:
$ man ./mdoc-examples.7
The resulting formatted output is visible in the following screencaps:
Note that we made no roff requests directly; only mdoc(7) macros were used.
The mdoc(7) man page contains a reference for the mdoc(7) macros (including which macros are parsed and/or callable), information about macro syntax, and a description of the standard sections of a man page and their order.
The man pages for other documentation varies depending on whether an OS is mandoc
-based or man-db
-based; refer to Appendix B for a list of the defaults for various OSes.
- reference for escape sequences to produce special characters: mandoc_char(7)
- reference for roff requests and syntax: mandoc_roff(7) / roff(7) (OpenBSD)
- reference for escape sequences to produce special characters: groff_char(7)
- reference for roff requests and syntax: groff(7)
Systems with mandoc(1) installed - regardless of whether it's being used as the man system - can use it to check mdoc(7) files for issues:
$ mandoc -T lint mdoc-examples.7
Refer to the DIAGNOSTICS section of the mandoc(1) man page for a detailed listing of errors, warnings and style issues.
The tbl language can be used to add tables to a roff document. mandoc(1) only accepts a subset of the tbl language; this subset is documented in the tbl(7) man page. However, groff(1) utilises a distinct tbl(1) program to format tables.
mdoc(7) man pages can be converted to other formats via the -T
option of mandoc(1) and groff(1). Available output formats include ascii
, html
, pdf
and utf8
; markdown
is available with mandoc(1) but not groff(1).
Some real-world examples of mdoc(7) sources can be found in the following repositories:
Thanks to "onf" for constructive feedback and criticism regarding issues with an earlier version of this document.
- [DO87] Dale Dougherty and Tim O'Reilly. Unix Text Processing. Hayden Books, 1987.
+----------+---------+------------+------------+-----------------+
| Date | System | Language | OS | Author(s) |
+----------+---------+------------+------------+-----------------+
| 1964 | RUNOFF | MAD | CTSS | Jerry Saltzer |
| 1967 | roff | BCPL | ? | Robert Morris |
| 1969 | runoff | BCPL | Multics | Douglas McIlroy |
| 1969 | roff | Fortran | System/360 | Brian Kernighan |
| 1972 | nroff | PDP-11 asm | Unix | Joseph Ossanna |
| 1973 | troff | PDP-11 asm | Unix | Joseph Ossanna |
| 1975 | roff | C | Unix | Brian Kernighan |
| 1979 | ditroff | C | Unix | Brian Kernighan |
| 1990 | groff | C++ | Unix | James Clark |
+----------+---------+------------+------------+-----------------+
Corrections to this table welcome and encouraged.
Ossanna's roff
system actually consisted of three formatter programs. Quoting roff.7 from the groff package:
troff
(typesetter roff) generated a graphical output for the [Wang] CAT typesetter as its only device.
nroff
produced text output suitable for terminals and line printers.
roff
was the reimplementation of the former runoff program with its limited features; this program was abandoned in later versions. Today, the name 'roff' is used to refer to a 'troff/nroff' system as a whole.
(troff
is pronounced "tee-roff"; nroff
is pronounced "en-roff".)
In the book "Unix Text Processing", published in 1987, the authors wrote (p.64):
The chief advantage of
troff
overnroff
is that it allows different types of character sets, or fonts, and so lets you take full advantage of the higher-quality printing available with typesetters and laser printers. There are a number of requests, useful only introff
, for specifying fonts, type sizes, and the vertical spacing between lines ...Later,
troff
was modified to support other typesetters and ... laser printers. The later version oftroff
is often calledditroff
(for device-independenttroff
), but many UNIX systems have changed the name of the originaltroff
tootroff
and simply callditroff
by the original name,troff
.
A detailed history is provided on the "History of UNIX Manpages" page. groff's roff.7 also provides a history.
OSes with a check mark ('✓') can display mdoc(7) man pages "out of the box", without any special configuration required on the part of the user.
+--------------+---+ -------------+--------------------------------------+
| OS | | man system | Notes |
+--------------+---+--------------+--------------------------------------+
| Adélie | ✓ | mandoc | |
| Alpine | ✓ | mandoc | |
| Arch | ✓ | man-db/groff | mandoc optional |
| Debian | ✓ | man-db/groff | |
| Fedora | ✓ | man-db/groff | mandoc optional |
| FreeBSD | ✓ | mandoc | Since 10.1 |
| Gentoo | ✓ | man-db/groff | mandoc optional |
| illumos | ✓ | mandoc | |
| macOS | ✓ | mandoc | Since 11 (November 2020) |
| | | | 13 made `man` a wrapper for `mandoc` |
| NetBSD | ✓ | mandoc | Since 6.0 |
| OpenBSD | ✓ | mandoc | Since 4.7 |
| openSUSE | ✓ | man-db/groff | mandoc optional |
| Ubuntu | ✓ | man-db/groff | |
| Void | ✓ | mandoc | |
+--------------+---+--------------+--------------------------------------+
Corrections and/or additions to this table welcome and encouraged.