less file1 file2 | cat -- why does it work?

less prints text to stdout. stdout goes

  • to a terminal (/dev/tty?) and opens the default buffer viewer
  • through a pipe when piping it to another programm using | (less text | cut -d: -f1)
  • to a file when redirecting it with > (less text > tmp)

There is a C function called "isatty" which checks if the output is going to a tty (less 4.81, main.c, line 112). If so, it uses the buffer viewer otherwise it behaves like cat.

In bash you can use test (see man test)

  • -t FD file descriptor FD is opened on a terminal
  • -p FILE exists and is a named pipe

Example:

[[ -t 1 ]] && \
    echo 'STDOUT is attached to TTY'

[[ -p /dev/stdout ]] && \
    echo 'STDOUT is attached to a pipe'

[[ ! -t 1 && ! -p /dev/stdout ]] && \
    echo 'STDOUT is attached to a redirection'

less checks if its stdout is a terminal, and behaves like cat when it isn't (copies stdin to stdout until EOF).

This feature lets you write scripts or programs that always send their output (e.g. --help output) through less while still allowing easy redirection to a file. It would suck if some_command --fullhelp > help.txt still waited for space-bar on stdin to page through the text, or something. Some commands (e.g. man) check that their own output to decide whether to send their output through a pager or not. If you run man ls > ls.txt, it never invokes your $PAGER.

less's cat-like behaviour is handy if you forget to edit it out of a one-liner when adding more stages to a pipeline, too.


less needs to figure out the terminal dimensions (screen size, to know how many lines to show at once). The ioctl(2) it uses on stdout would return ENOTTY on a non-terminal, so it can't avoid handling the non-terminal case anyway. less actually uses isatty(3) before checking the terminal dimensions, but isatty works by trying a tty-only ioctl and checking for lack of error.

Even a simple pager like more(1) (at least the util-linux version) has this feature, because it's probably the simplest sane behaviour to implement for that case.


Note that when you pipe something into less (e.g. grep foo bar.txt | less), it does have to open /dev/tty for keyboard input. (You can see it do this with echo foo | strace less).

Tags:

Pipe

Cat

Less