How to write Unix man pages for macOS command line apps

Over the last few years I’ve released a number of command line utilities for macOS. I’ve always included online help within them, triggered with the --help switch, but I recently wondered how I might provide Unix Manual pages too. It would allow users to call up help with the CLI command man as well as a command switch. Belts and braces, perhaps, but I’m a completist and, more to the point, didn’t know how it was done and wanted to learn.

If you Google “man page creation” — the logical next step — you’ll find there’s a lot of material that covers aspects of the process: where to place files so man can access them and cheatsheets for the troff markup of which man uses a form. But there’s little on packaging man pages with your app. So this is my attempt to bring all these elements together into a single post.

Manual Basics

Unix man pages are organised according to “Sections” within a notional monolithic Unix Manual. So, for example, all the commands — ie. programs — that you would run as a user to perform a non-system task are detailed in Section 1 of this manual, and that’s why you’ll see their manual pages expressed as, say, utility(1).

These are the manual sections:

  1. User commands.
  2. System calls, ie. commands which wrap operations performed by the kernel.
  3. Library functions.
  4. Device files, ie. those found in /dev.
  5. File formats and configuration files.
  6. Games
  7. Miscellaneous documents, ie. those describing coding conventions, protocols or system information.
  8. System management commands.

It’s a logical way to organise manual pages and it gives you an indication of the function of the entity that’s being described. Equally, it’s mystifying at the start — what do all those numbers mean, you wonder? And why do some utilities have pages in multiple sections? Because they need it. The job scheduling tool crontab, for example, has a crontab(1) page to describe the utility itself and a crontab(5) page which tells you how to work with the utility’s configuration files.

Tip If you want to view a utility’s page within a specific section, include the section number before the name of the utility. For example, man 5 crontab.

The page files themselves are typically suffixed with a stop and the section number they’re filed under. So, in my case, pdfmaker is a utility program so it’ll be assigned to Section 1 and its filename will be pdfmaker.1.

Writing Pages

Manual pages are plain text files that are formatted using an old and arcane format from the early years of Unix. But it’s not hard to pick up, and you’ll likely find you need no more than a handful of markup commands, which I’ll list in a moment.

There are alternatives to writing pages from scratch, of course. I tried help2man, a utility that reads in app’s --help output and generates a man page from it. A good place to start, but I found I still needed to do work on the formatting afterward. A defunct utility call ronn and a reawakened version, ronn-ng, seek to convert text formatted with Markdown into man pages, as does Pandoc. I’d start with either of these if you’ve written a large amount of help information already and you want to convert it to manual form quickly.

However, let’s crack on with the basic formatting codes. The first, and crucial one, is .TH which specifies the page’s header and footer information. It comes right at the start — only comments can come before it. For an example, here’s the top of my pdfmaker page:

.TH "PDFMAKER" "1" "May 2022" "pdfmaker 2.3.3 (50)"

The fields are, respectively, page title, manual section, date and the source app. The first two fields appear in the header combined, as PDFMAKER(1), the rest in the footer:

The next code is .SH, short for Section Heading. The single word or phrase (the latter in quotes) that follows it (immediately or on the next line) is rendered bold and ranged left. Use this to mark out the key NAME, SYNOPSIS, DESCRIPTION, OPTIONS and SEE ALSO sections found in any good man page, though there are others you may use. Here’s an example:

pdfmaker \- Create PDFs from images.

The slash is used to escape the minus symbol that follows. You’ll see more escaped characters in a moment. The expected structure of the NAME text is app_name – brief_description. It renders like this:

Paragraph starts are marked with a .P. There are variations on this tag, such as .LP, .IP and .TP, but I use .P. It renders the lines that follow it as an indented block of text, stopping only when another paragraph or heading marker is reached. You can split the words and lines as you prefer (well, almost) and man will lay out the paragraph evenly. So this content:

\fBpdfmaker\fR can be used to convert
a directory of images or a specified image to a single PDF file, or
a single PDF file into a collection
of image files.

will render as:

The exception is when man encounters spaces at the start of a line — these are rendered as is, which is handy if you need to manually indent a line or two:

\fB\-c\fR | \fB\-\-compress\fR {amount}   Apply an image compression filter to the PDF:
                           \fI0.0\fR = maximum compression, lowest image quality.
                           \fI1.0\fR = no compression, best image quality.

which yields:

You’ll undoubtedly have worked out that the \fB and \fR are formatting markers — B for bold and R for Roman, ie. back to plain text. You also have \fI which is nominally for italic but is usually rendered as an underline (but only for non-space characters — escape the spaces ‘\ ’ to fix that). Why the italic-underline binding? Because underlining is the old typesetting marker for italic. Not to indicate emphasis — as it’s often misused — but to tell a typesetter to set the marked text in an italic font.

.B, .I and .R are alternative forms of this formatting, marking the immediately following word or phrase, or the text in the line below:

This is some roman text with a
.B bold
word in between and an
one too.

which renders as:

Use whichever approach — dot codes or escapes — that suits you best. I prefer the latter because they’re easier to fit into an existing line structure with manual indents. All dot codes have to appear at the start of a line, as shown in the examples here.

And that’s pretty much it. You can find out about more codes in section 7 of man’s own manual, though not on macOS — you’ll find it online, however. The one other key markup component you might want to use is the comment marker, .\". It works the same as // in C-style languages, or # in Python and shell scripts.

Other codes you might want to use include .SS, which defines a subheading, ie. like an .SH heading but set further in; .RS and .RE, which mark the start and end of a text block indented five spaces more than the current indentation level; and .EX and .EE, which are used to mark out blocks of example code.

Tip You can test your text file with man itself: just call man and pass the path to your file as its argument. Check and adjust your formatting until you’re happy with how it’s rendered.

There’s a good list of man format codes here.

Page Installation

To distribute the page you’ve just written, you need to add it to your chosen packaging workflow. I use Homebrew, which makes installing apps easy and handles man-page installation for you too. Just include your man page in your .dmg file and include the following line in your Cask file:

manpage "<path/to/your/manfile/in/your/dmg>"

On macOS, man pages from user applications are typically found in /usr/local/share/man/ where there are directories for one or more man sections. The directories are named manXX is the section number. Homebrew drops symlinks to man pages it has installed in that directory on Intel Macs, but on Apple Silicon machines, the base directory is /opt/homebrew/share/man. You may find other pages under /usr/local/share/man/ if you’ve installed command line utilities by other means than Homebrew.

Tip You can see where man looks for page files by viewing /etc/man.conf.

If you don’t use Homebrew, you can include a shell script with your code that not only puts your app in place but also copies your man file into /usr/local/share/man/manX.

Alternatively, if you distribute your app as macOS Installer package (.pkg), you can include a script called postinstall and which you’ve coded to write out the app’s man page. Installer will run this script automatically after installing the application. Build the package with the command line tool pkgbuild and include the --scripts option — its argument is the path to your script’s source directory. Your postinstall script can include the man page text verbatim and just call cat to write it out. Here’s an example for a utility that should be included in Section 1 of the Unix Manual:

if [[ ! -d /usr/local/share/man/man1 ]]; then
    mkdir -p /usr/local/share/man/man1
cat <<EOT >> /usr/local/share/man/man1/my_utility.1
.TH MY_UTILITY "1" "May 2022" "my_utility 1.0.0 (1)"
exit 0

The script needs to executable, of course, and that last, explicit exit 0 is essential, or Installer will report a failed installation.

Looking for inspiration for your own man pages? Check out my imageprep and pdfmaker GitHub repos for their raw man page files.