17

While creating a bash script, I found that this code ls puts all files on one line:

pi@raspberrypi:~/ptlrestinterface$ ls
update.sh   web.config   MyApp.runtimeconfig.json   

still ls | head -n1 still prints only the first file:

pi@raspberrypi:~/ptlrestinterface$ ls | head -n1
update.sh

I would expect it to output the entire line, not the first file.

While piping the output through hexdump, I saw ls always put out a 0a after each entry, however on the console, it places them next to each other.

Apparently ls has some knowledge of the console (or the console of ls). How does this work, and where could I find documentation on this behaviour?

Bart Friederichs
  • 1,876
  • 6
  • 25
  • 43

2 Answers2

31

It calls the isatty() libc function on its stdout file descriptor to determine whether the output goes to a terminal or not.

Internally, isatty() attempts to use one of the ioctl() calls that are specific to tty devices – most likely ioctl(TCGETS) (which retrieves the generic tty settings that the stty command shows); if it succeeds, then the file is a tty device.

$ python
>>> sys.stdout.isatty()
True

This affects both whether ls will columnate its output by default, and whether it will use colors by default, and some other things such as whether file names will be quoted.

If stdout is indeed a terminal, then ls uses another tty-specific ioctl(TIOCGWINSZ) to retrieve its dimensions (which on modern systems have been stored there by the terminal emulator) to find out how many columns it can fit. The stty size command also gets those; try running it under strace.

Full-screen programs additionally react to the SIGWINCH signal which the kernel sends every time the terminal emulator updates the dimensions of the tty, as an indication that they need to redraw everything in the new size.

grawity
  • 501,077
10

For a less technical explanation, observe what happens when you try these two commands:

$ ls
update.sh   web.config   MyApp.runtimeconfig.json

$ ls | cat update.sh web.config MyApp.runtimeconfig.json

Piping through cat "does nothing", except that ls "notices" that it's output is being piped somewhere. So it changes its behavior accordingly.

Once you understand what ls is outputting, it's straightforward what happens when it passes through head -n1.

gbarry
  • 848