groff turns a page of dot commands and macro calls into the man page, PDF or plain text you actually wanted to read. This walks you through formatting a small roff document, rendering an installed man page for a terminal, and inspecting the exact command pipeline that groff builds behind the scenes. The examples match GNU groff 1.23.0 from Debian package groff-base 1.23.0-3build2. Allow about fifteen minutes if you have a text file ready, and skip sudo: everything here is an ordinary, unprivileged command.
Start by checking which executable your shell will run and which package version is installed:
$ command -v groff
/usr/bin/groff
$ dpkg-query -W -f='${Package} ${Version}\n' groff-base
groff-base 1.23.0-3build2
$ groff --version | sed -n '1,2p'
GNU groff version 1.23.0
The version matters because GNU groff is only a front end: it wires together troff, preprocessors such as tbl, and an output driver. The available drivers come from whatever package set is installed, so do not assume every device the manual describes is present on a minimal system.
Roff input is plain text with control lines beginning with a full stop. This example uses the man macro package and the UTF-8 terminal device. The pipe removes blank page lines so the result is easy to read:
$ printf '.TH demo 1\n.SH NAME\ndemo - test document\n.SH DESCRIPTION\nHello from groff.\n' \
| groff -Tutf8 -man | sed '/^$/d'
demo(1) General Commands Manual demo(1)
NAME
demo - test document
DESCRIPTION
Hello from groff.
Exact spacing and control sequences vary with the terminal and pager. -Tutf8 selects terminal output encoded as UTF-8. -man loads the supplied manual-page macros; it is not a request to read a file from the system man-page database.
Checkpoint: if you see the title and the two headings, the formatter and the terminal output driver are working. If the output shows raw escape sequences in a plain log, use a pager that handles them, such as less -R, or try -Tascii.
Man pages are commonly compressed, and groff will not decompress a .gz operand for you. Decompress the source into the pipe, then let groff run the tbl preprocessor and the man macros:
$ zcat /usr/share/man/man1/groff.1.gz \
| groff -t -man -Tutf8 \
| less -R
Press q to leave less. -t runs tbl, needed when the source contains tables. Nothing here touches the compressed man page or writes a new file; in normal use, the man command assembles an equivalent pipeline and picks a pager for you.
Want a saved text copy instead of an interactive pager? Redirect the final output:
$ zcat /usr/share/man/man1/groff.1.gz \
| groff -t -man -Tutf8 > /tmp/groff-man.txt
$ test -s /tmp/groff-man.txt && sed -n '1,12p' /tmp/groff-man.txt
The temporary file is disposable. Remove it once you have finished inspecting it; nothing here needs elevated privileges.
When a document fails, the useful question is usually which helper got picked. Add -V to print the pipeline without executing it:
$ printf 'Hello from groff.\n' | groff -V -Tutf8
troff -Tutf8 | grotty
Your output may show a different path or extra options, but -V only reports the pipeline; it never runs it. Reach for this before splitting a complex command into separate stages. The manual also documents -w and -W for selecting and suppressing warning categories; -ww turns on every warning when you are experimenting.
Checkpoint: if -V names an executable that is missing, fix the installed package set or your command search path before touching the document. Do not paste a reported pipeline into a privileged shell just because it came out of a document.
The manual lists devices including ascii, utf8, ps, pdf, html and dvi, but groff-base does not guarantee all of them. On this host the terminal driver works, but the minimal package is missing the PDF and HTML device description files:
$ printf 'Hello from groff.\n' | groff -Tpdf > /tmp/hello.pdf
groff: fatal error: cannot load 'DESC' description file for device 'pdf'
$ printf 'Hello from groff.\n' | groff -Thtml
groff: fatal error: cannot load 'DESC' description file for device 'html'
groff ever reports the failure.file.Installing packages changes system state, so treat that as its own reviewed step rather than something to bolt onto this guide.
Safer mode is on by default; -S selects it explicitly. The opposite, -U, enables unsafe mode for pic and troff. Leave -U out unless you have read the source and understand exactly why the document needs it: roff source can include requests and preprocessors that read files or trigger other behaviour, so do not format untrusted input in unsafe mode.
Likewise, -l sends output to a print spooler when the selected device defines one, which is a real-world side effect unlike the terminal examples above. Test with a file or a terminal device first, and check the selected device and spooler arguments before using -l on a production host.
groff returns success when its whole processing pipeline succeeds. Its failure status encodes whether a pipeline command failed, was killed by a signal, or could not be executed at all. Capture the status immediately:
$ printf 'Hello from groff.\n' | groff -Tutf8 > /tmp/hello.txt
$ status=$?
$ printf 'groff status: %s, bytes: %s\n' "$status" "$(wc -c < /tmp/hello.txt)"
groff status: 0, bytes: 83
Recovery: for a failed conversion, keep the original source and write to a new temporary destination, check the new file, then replace an old output with an explicit mv. If a command fails, remove the incomplete temporary file and rerun after fixing the cause. Do not overwrite a known-good document with a failed or empty render.
groff --version matches the installed 1.23.0 behaviour described here.-Tutf8 and inspect it in a terminal.groff.-V shows the selected pipeline without executing it.