Why does “ls | wc -l” show the correct number of files in current directory?

Trying to count number of files in current directory, I found ls -1 | wc -l, which means: send the list of files (where every filename is printed in a new line) to the input of wc, where -l will count the number of lines on input. This makes sense.

I decided to try simply ls | wc -l and was very surprised about it also gives me a correct number of files. I wonder why this happens, because ls command with no options prints the filenames on a single line.

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

From info ls:

‘-1’
‘–format=single-column’

List one file per line. This is the default for ‘ls’ when standard
output is not a terminal.

When you pipe the output of ls, you get one filename per line.
ls only outputs the files in columns when the output is destined for human eyes.


Here’s where ls decides what to do:

  switch (ls_mode)
    {
    case LS_MULTI_COL:
      /* This is for the 'dir' program.  */
      format = many_per_line;
      set_quoting_style (NULL, escape_quoting_style);
      break;

    case LS_LONG_FORMAT:
      /* This is for the 'vdir' program.  */
      format = long_format;
      set_quoting_style (NULL, escape_quoting_style);
      break;

    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;

    default:
      abort ();
    }

source: http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/ls.c

Method 2

Because the output of ls depends on the std output, it is different for terminal and pipe. Try

/bin/ls | cat

Method 3

Historically, ls wrote its output one file per line, which is a convenient format for processing with other text-based Unix tools (like wc). However, on a 24 line terminal with no scrollback, large listings had a tendency to scroll off the screen, making it hard to find what you were looking for. So, at some point, the BSD developers changed the behavior so, when printing to a terminal, ls would format its output in multiple columns. The old behavior was retained when writing to a pipe or a file to avoid breaking existing shell scripts — and because the old behavior is more useful when processing the output with a command like wc. The decisions to incorporate the multi-column output into ls and to make it the default on the terminal, exercised Rob Pike quite a bit; Research Unix didn’t pick up the new features until 8th Edition (which was based directly on BSD) and Plan 9 reverted to separate commands, ls for scripts and lc for interactive use, with lc a shell script calling ls and a command mc providing multi-column output.

The -1 and -C options to ls are a belated attempt to restore sanity, by at least allowing the user to force a specific output format regardless of the output destination.

Method 4

Why does “ls | wc -l” show the correct number of files in current
directory?

Well, that’s a false premise right there. It does not! Try this:

mkdir testdir
cd testdir
# below two lines are one command, the newline is quoted so will be part of argument
echo text | tee "file
name"
ls -l
ls | wc -l

Output of that last line is 2.

Note how, when printing to the console in ls -l command, ls will not print the newline as is, but will instead print ?. But this is a specifically implemented feature of ls, it does this when it detects the output is going to an actual terminal, to avoid funny file names from messing the terminal up. This same detection determines if file names are printed one-per line (in pipe) or according to terminal width (which obviously only makes sense if there is a terminal with width). You can fool ls with command like ls | cat if you want the raw file names printed, separated with newlines.

wc -l just counts number of lines, and if a file name happens to contain a newline, well then wc will count it as two lines.


ls also has switches to force hiding control chars, -q/--hide-control-chars, so ls -q | wc -l should actually give accurate number of files listed by ls (which usually is not same as actual number of files in the directory, without -a switch), because then only newlines in ls output should be those separating file names.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x