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):


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?


4 Answers 4


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);

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.)


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

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.


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

You must log in to answer this question.

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