5

When I do, say, a

man zsh

I get the zsh man page, parts of the text are in bold or underline, which makes it for easier reading. man is doing this for instance by inserting 0x08 control codes in its output, which then is interpreted by the Terminal for rendering underlined text.

When I do a

man zsh | cat

I of course don't see such decorations anymore, but just the plain text. This makes sense, because the program reading from the pipe, is usually not interested in seeing those control codes.

I assume that man checks whether the stdout is a tty or a pipe/file, an in the latter case it does not produce the output.

Now comes the puzzling part:

When I do a

man zsh | less
man zsh | xxd
man zsh | awk ....

I do still see the decorations (underline etc.) at the receiving end of the pipe. How is this possilble? man does not know whether the process it pipes the output to, wants to see the control code, and cat and head certainly don't have the ability to filter out such decorations.

7
  • Man pages are formatted using the roff typesetting standard. cat and head are not roff formatters.
    – Allan
    Commented May 15, 2023 at 16:35
  • 1
    @allan neither is less :-)
    – nohillside
    Commented May 15, 2023 at 17:03
  • 2
    So more or less, this doesn’t format correctly @nohillside? ;-). (Sorry, I couldn’t resist)
    – Allan
    Commented May 15, 2023 at 17:07
  • @allan Right. It get's even more interesting, did you try man zsh | cat | less?
    – nohillside
    Commented May 15, 2023 at 17:11
  • 1
    Which basically leads to the answer that it's not man knowing what is at the other end of the pipe but rather cat suppressing control information on output.
    – nohillside
    Commented May 15, 2023 at 17:16

2 Answers 2

9

The theory

The man program itself, doesn't talk directly to the terminal. It produces output which is text with a small amount of formatting. Another program, a pager, generally less, handles the user interaction (scrolling, searching, etc.) and applies some basic formatting.

In addition to basic pagination (breaking lines, indenting, etc.), man can use three ways to make text stand out: capital letters, bold and underline. Capital letters are just different characters from lowercase letters. For bold and underline, man uses the backspace (BS) control character:

  • The three-character sequence c BS c indicates that the character c should be displayed in bold.
  • The three-character sequence _ BS c (where _ is the underscore character) indicates that the character c should be displayed underlined.

(There is no provision for bold underscore, and I don't know offhand which way _ BS _ would be interpreted.)

These sequences are inspired from what you could do with a typewriter, and in particular with a teletype which was a common interface to computers when the man program was originally written: if you print a character, then use the BS control to move back left and print that character again, you get a bolder character. If you use BS to overstrike a _ with another character, that other character is underlined.

Character-oriented video terminals don't overstrike characters in this way, and modern terminal emulator programs present a similar interface. Terminals interpret the BS character as an instruction to move left by one position. When displaying a character at a position, the terminal completely overwrites whatever was previously there.

So it's the job of the pager to interpret convert c BS c and _ BS c to instructions for the terminal to display c in bold or underline. The way to do this depends on the terminal capabilities, although in practice all modern terminals use the same escape sequences for basic functionality like bold and underline.

The practice

When you run man zsh, the man command invokes less. less converts BS-based bold/underline into formatting escape sequences that the terminal understands. So you see the text formatted as intended.

When you run man zsh | cat, the man program detects that its output is not a terminal, so it doesn't run less: it just prints the output that it would have passed to less. cat just passes its input through unchanged. The terminal sees sequences like c BS c and _ BS c, and in either case, BS erases the previous character so the visual effect is just like a plain c.

When you run man zsh | less or man zsh | cat | less, the less command receives the same input that it would receive if man had invoked less directly. However, the behavior may be different because man can invoke less with some non-default command options. (From a quick check, this doesn't appear to happen on macOS.)

You can disable less's handling of backspace by passing the -u command line option. Run either LESS=-u man man or man man | less -u to see what happens if less just passes the BS character unchanged to the terminal: the visual effect is the same as when not running less (but less still does pagination). Run LESS=-U man man or man man | less -U to see a visual rendering of the BS character as^H in reverse video.

Other ways to see the BS characters include:

  • man man | cat -v which translates each BS character into the two-character sequence ^H.
  • man man | hd which shows a hex dump of the text. You'll see sequences like 4e 08 4e 41 08 41 4d 08 4d 45 0808=backspace, 4e=N, 41=A, 4d=M, 45=E so this is NAME in bold.
  • man man >out.txt and open out.txt in an editor. How the editor shows control characters depends on the editor and possibly on its configuration.

If you want to print your own, you can pass \b or \010 in the first argument of the printf terminal command to print a backspace character. Try these:

printf 'Normal\nB\bBo\bol\bld\bd\n_\bU_\bn_\bd_\be_\br_\bl_\bi_\bn_\be\n'
printf 'Normal\nB\bBo\bol\bld\bd\n_\bU_\bn_\bd_\be_\br_\bl_\bi_\bn_\be\n' | cat
printf 'Normal\nB\bBo\bol\bld\bd\n_\bU_\bn_\bd_\be_\br_\bl_\bi_\bn_\be\n' | cat -v
printf 'Normal\nB\bBo\bol\bld\bd\n_\bU_\bn_\bd_\be_\br_\bl_\bi_\bn_\be\n' | hd
printf 'Normal\nB\bBo\bol\bld\bd\n_\bU_\bn_\bd_\be_\br_\bl_\bi_\bn_\be\n' | less
printf 'Normal\nB\bBo\bol\bld\bd\n_\bU_\bn_\bd_\be_\br_\bl_\bi_\bn_\be\n' | less -u
printf 'Normal\nB\bBo\bol\bld\bd\n_\bU_\bn_\bd_\be_\br_\bl_\bi_\bn_\be\n' | less -U
1

By default, cat strips control characters when the output goes to a terminal. From man cat:

-e  Display non-printing characters (see the -v option), and display a dollar
    sign (‘$’) at the end of each line.

-t  Display non-printing characters (see the -v option), and display tab
    characters as ‘^I’.

-v  Display non-printing characters so they are visible.  Control characters
    print as ‘^X’ for control-X; the delete character (octal 0177) prints
    as ‘^?’.  Non-ASCII characters (with the high bit set) are printed as ‘M-’
    (for meta) followed by the character for the low 7 bits.

So, running

  • man man | cat will strip control characters,
  • man man | cat -e will show them (as will cat -v and cat -t),
  • man man | cat | less will pass control characters to less (making the cat part basically a no-op).
7
  • If the PAGER variable in /private/etc/man.conf is set to less (mine is on Catalina) then piping to less again will have no effect, just as the OP saw when he did man zsh | less
    – Allan
    Commented May 15, 2023 at 17:28
  • @allan less is the default pager for man :-) But man is a distraction here, the same would happen if you run troff -options input.troff | ....
    – nohillside
    Commented May 15, 2023 at 17:32
  • 2
    No, cat does not strip control characters. It leaves them unchanged. The options you cite replace control characters by sequences of visible characters, and will not produce bold or underline text. Commented May 15, 2023 at 18:22
  • 2
    Sure, it doesn't show them: the terminal treats the backspace character as an instruction to move one position to the left, overwriting the previous character, so c1 BS c2 has the same visual effect as just c2 and you can't tell that man wrote c1 BS c2. But man did indeed write c BS c for a bold character c and _ BS c for an underlined c. Commented May 15, 2023 at 19:44
  • 1
    All you have to do is enter the command man echo | cat | hexdump -Cv to see @Gilles'SO-stopbeingevil' is correct. Commented May 15, 2023 at 23:00

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .