12

How does commands like ls know what its stdout is?

It seems ls is operating different depending on what the target stdout is. For example if I do:

ls /home/matt/tmp 

the result is:

a.txt b.txt c.txt

However if I do

ls /home/matt/tmp | cat

the result is (i.e. new line per result):

a.txt
b.txt
c.txt

The process is passed a file descriptor 1 for stdout right? How does it determine how to format the result? Does the file descriptor reveal information?

  • Related http://unix.stackexchange.com/q/157285/4671, http://unix.stackexchange.com/q/63108/4671 and probably others. It seems to be a popular topic. This might be a dupe of one of these. – Faheem Mitha Feb 27 '15 at 22:06

4 Answers4

22

The ls program uses isatty() to know whether fd 1 is a tty or something else (pipe, file, etc…). From man 3 isatty:

int isatty(int fd);

DESCRIPTION
The isatty() function tests whether fd is an open file descriptor referring to a terminal


Updade: Line 1538 in ls.c from coreutils (git revision 43a987e1):

  if (isatty (STDOUT_FILENO))
    {
      format = many_per_line;
      /* See description of qmark_funny_chars, above.  */
      qmark_funny_chars = true;
    }

(many_per_line should be self-descriptive.)

8

Not an exact answer but an exemplification. In a Bash script you can achieve similar effect with test/[['s -t:

-t FD True if FD is opened on a terminal.

Using it like this:

bash-4.2$ where() { [[ -t 1 ]] && echo 'my output goes to TTY' || echo 'my output is redirected'; }

bash-4.2$ where
my output goes to TTY

bash-4.2$ where | cat
my output is redirected

bash-4.2$ where > test.file
bash-4.2$ cat test.file
my output is redirected
manatwork
  • 31,277
6

From the OpenBSD ls(1) manual:

By default, ls lists one entry per line to standard output; the exceptions are to terminals or when the -C, -m, or -x options are specified.

Then, later:

-1 (The numeric digit ``one''.) Force output to be one entry per line. This is the default when output is not to a terminal.

[...]

-C Force multi-column output; this is the default when output is to a terminal.

Kusalananda
  • 333,661
1

You may execute ls in a pseudo-terminal using the script command, pipe the output of ls to another command and get the same output format as if there was no such piping of the stdout stream, i.e. as if stdout were a terminal (tty).

For the underlying isatty() mechanism already pointed out by Stéphane Gimenez see ls.c.

ls -G /
ls -G / | cat
script -q /dev/null ls -G / | sed $'s/\r$//g' | cat

# tty | cat
# script -q /dev/null tty | cat
ron
  • 11