Turn nroff Underlining into Readable Terminal Output with ul

ul turns the overstrike underlining that nroff produces into something your terminal can show, or into visible marker lines for inspection. This guide uses /usr/bin/ul from Ubuntu's bsdextrautils package, version 2.39.3-9ubuntu6.6. Allow about ten minutes. You need a shell, a terminal type known to terminfo, and some nroff-style underlined text.

Checkpoint: this machine also has a separate util-linux 2.42.4 binary earlier on one user's PATH. Run command -v ul before copying examples into a script. The examples below use /usr/bin/ul so the executable matches the package and manpage described here.

1. Create a small nroff-style test

An underlined character in nroff output is usually a character, a backspace, then an underscore. In a shell command, write the backspace as \b inside Bash's $'...' syntax:

$ printf '%s\n' $'u\b_nderlined text' > /tmp/ul-input.txt
$ od -An -tx1c /tmp/ul-input.txt

The od check should show the bytes for u, backspace and underscore in that order. The temporary file is ordinary user data and safe to remove once you have looked:

$ rm /tmp/ul-input.txt

If you are processing a real command's output instead, leave that source alone: ul reads input and writes translated output, it does not edit the named input file.

2. Render the underlining for your terminal

Set TERM to the value belonging to the terminal where you will actually read the output. Here, xterm is only a reproducible example:

$ printf '%s\n' $'u\b_nderlined text' | TERM=xterm /usr/bin/ul

On a terminal that supports the terminfo description, the output shows as underlined text with the first character underlined. The exact control bytes are terminal-dependent: the command reads TERM, consults terminfo, and picks the terminal's underlining sequence, falling back to standout mode when underlining is not available.

Tip: if the result looks plain, do not assume the input is wrong. Inspect the byte-level output first:

$ printf '%s\n' $'u\b_nderlined text' | TERM=xterm /usr/bin/ul | od -An -tx1c

With a working xterm description, you should see escape bytes around the underlined character. The screen may not visibly show them when output is redirected or displayed by a program that does not interpret terminal controls.

3. Make underlining visible with --indicated

Use -i, also written --indicated, when you want to see where underlining occurs rather than activate terminal formatting:

$ printf '%s\n' $'u\b_nderlined text' | TERM=xterm /usr/bin/ul --indicated

Expect two lines: underlined text, then a dash under the underlined position:

underlined text
_

Reach for this when you are examining an nroff stream in a log, test fixture or captured output, and want a marker you can trust rather than whatever a terminal emulator decides to render.

4. Process a file or standard input

With a filename, ul reads that file. With no filename, it reads standard input, so it drops straight into a pipeline:

$ TERM=xterm /usr/bin/ul /path/to/nroff-output.txt
$ nroff -man /path/to/page.1 | TERM=xterm /usr/bin/ul

Use a real, readable path in place of /path/to/.... The second command is an ordinary, unprivileged pipeline as long as the source file is readable: do not add sudo just to make terminal formatting work, elevated privileges do not repair terminal capabilities or terminfo.

ul can also sit after a command that emits formatted manual-page text. If you are already using man, prefer its normal pager and terminal handling unless you specifically need to inspect the intermediate nroff stream: ul is a formatter for that stream, not a replacement for a pager.

5. Choose a terminal type explicitly when needed

The -t, -T and --terminal options override TERM for one invocation:

$ printf '%s\n' $'u\b_nderlined text' | /usr/bin/ul --terminal xterm

Use this when the environment has the wrong terminal type set, for example inside a controlled test. Do not guess a terminal name in a production script: the selected name needs a usable terminfo entry, or the command reports a terminal-description error, or simply cannot give you the formatting you expected.

Where possible, fix the environment at the boundary that set it, rather than forcing --terminal everywhere. A remote session, multiplexer or redirected output may legitimately use a different terminal capability set. Check the current value without changing it:

$ printf 'TERM=%s\n' "$TERM"
$ infocmp "$TERM" > /dev/null

If infocmp fails, that is a terminfo installation or terminal-name problem, not a reason to run the formatter as root.

6. Handle plain output and common mistakes

If the terminal cannot underline, ul just ignores underlining. If it can overstrike or handles underlining automatically, the command effectively behaves like cat. That fallback is why a successful run can still look completely unformatted.

Three checks catch most wasted debugging time:

Ask the installed binary about its own interface if you are reviewing a script:

$ /usr/bin/ul --version
ul from util-linux 2.39.3
$ /usr/bin/ul --help

Option names are not a substitute for checking the installed version. Do not copy options from a different formatter or a shell alias: this manpage documents only the file operands, -i, the terminal override, help and version.

Done means