4

When ls is called, it outputs all the files/directories in the current directory, attempting to fit as many as possible on each line. Why is it that when passed to wc -l, it outputs the number of files? How does it decide how many lines to output its results in?

howard
  • 1,342
  • 1
  • 11
  • 16
  • 3
    Try ls | cat to see that ls's output is different when it detects that its output is redirected. – manatwork Jan 30 '13 at 16:30
  • The COLUMNS environment variable generally afftects the width of screen-formatted output. – tripleee Jan 30 '13 at 16:32
  • Several commands will react differently if called from an interactive terminal. Depends on the command, GNU ls is one clear example, as you've found out. wget will use a dotted progress display, instead of the "animated" progress bar you get in "interactive mode". – njsg Jan 30 '13 at 18:00
  • @manatwork, not "output redirected" but "output isn't a tty". – vonbrand Jan 30 '13 at 20:36
  • @njsg, this isn't "GNU ls", it is POSIX-mandated behaviour. Right on the money on wget, thhough. – vonbrand Jan 30 '13 at 20:38

2 Answers2

11

When ls is executed it parses various options. It also detect if output is a tty or not by isatty().

ls.c:

code

case LS_LS:
  /* This is for the `ls' program.  */
  if (isatty (STDOUT_FILENO))
    {
      format = many_per_line;
      /* See description of qmark_funny_chars, above.  */
      qmark_funny_chars = true;
    }
  else
    {
      format = one_per_line;
      qmark_funny_chars = false;
    }
  break;

...

code

      /* disable -l */
      if (format == long_format)
        format = (isatty (STDOUT_FILENO) ? many_per_line : one_per_line);

etc.


If you want you can compile a simple test:

isawhat.c

#include <stdio.h>
#include <unistd.h>

int main(void)
{
    if (isatty(STDOUT_FILENO)) {
        fprintf(stdout, "Word by word my world.\n");
    } else {
        fprintf(stdout, "HELP! Stranger handling my words!!\n");
    }

    fprintf(stderr, "Bye bye.\n");

    return 0;
}

Compile by:

gcc -o isawhat isawhat.c

Then e.g.:

$ ./isawhat | sed 's/word/world/'

Width is measured in columns. One column is one character. It starts out with 80, then check if the environment variable COLUMNS is set and holds a valid int that is not larger then SIZE_MAX (Which is arch dependant - your terminal will never be that wide (at least not yet)).

Try e.g. echo $COLUMNS. It most probably reflect the number of columns you have available in the window. As window get resized - this get updated. It most probably also get reset by various commands.

One way to set it a bit harder is by stty. E.g. stty columns 60. Use stty -a to view all (man stty). A fun piece of software.

If compiled in it also query for columns by ioctl(), Window size detect.. By passing the filenumber for stdout to ioctl and passing the request TIOCGWINSZ the structure winsize get filled with the number of columns.

This can also be demonstrated by a simple c-code:

Compile, run and resize window. Should update. Ctrl+C to quit.

#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <signal.h>

static int run;

void sig_handler(int sig) {
    switch (sig) {
    case SIGINT:
    case SIGTERM:
    case SIGSTOP:
        run = 0;
        break;
    }
}

void sig_trap(int sig) {
    if ((signal(sig, sig_handler)) == SIG_IGN)
        signal(sig, SIG_IGN);
}

int main(void)
{
    struct winsize ws;

    sig_trap(SIGINT);
    sig_trap(SIGTERM);
    sig_trap(SIGSTOP);

    run = 1;
    while (run) {
        if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1) {
            fprintf(stdout, "\r %s: %3d, %s: %d\r",
                "Columns", ws.ws_col,
                "Rows", ws.ws_row
            );
            fflush(stdout);
        }
        usleep(5000);
    }
    fprintf(stdout, "\n");

    return 0;
}
Runium
  • 28,811
1

When output is directed into a file descriptor other than a terminal (thus pipe, or file, or the like), ls behaves as if it was invoked as ls -1.