22

ls returns output in several columns, whereas ls|cat returns byte-identical output with ls -1 for directories I've tried. Still I see ls -1 piped in answers, like ls -1|wc -l. Is there ever a reason to prefer ls -1? Why does ...|cat change the output of ls?

  • 1
    filenames could contain newlines, so you would count those several times... you could instead posixly do: n=0; for i in .* *; do ((n++)) ; done ; echo $n (drop the .* if you don't want to count those ones). or : ls -1d ./.* ./* | grep '^\./' | wc -l (as filenames can not contain '/') – Olivier Dulac Dec 13 '17 at 12:49
  • 3
    ls output to a terminal commonly includes color codes by default. For output to a non-terminal, color is typically disabled by default. In GNU, that's --color={always,auto,never} IIRC. If color is included in one but not another, then the outputs may appear identical on screen, but they aren't byte-identical (color codes form part of the output of ls). – user Dec 13 '17 at 14:16
  • @MichaelKjörling I think you should write that as an answer. – 200_success Dec 13 '17 at 18:06
  • @MichaelKjörling That's something interesting I haven't considered. Is there a general way to always pipe as if outputting to terminal without having to remember the terminal options for color and column output, etc.? – Johannes Riecken Dec 20 '17 at 01:08
  • @rubystallion That sounds like a good separate question. – user Dec 20 '17 at 08:04

3 Answers3

29

ls tests whether output is going to a terminal. If the output isn't going to a terminal, then -1 is the default. (This can be overridden by one of the -C, -m, or -x options.)

Thus, when ls is used in a pipeline and you haven't overridden it with another option, ls will use -1. You can rely on this because this behavior is required by POSIX

POSIX Specification

POSIX requires -1 as the default whenever output is not going to a terminal:

The POSIX spec:

The default format shall be to list one entry per line to standard output; the exceptions are to terminals or when one of the -C, -m, or -x options is specified. If the output is to a terminal, the format is implementation-defined.

Those three options which override the default single-column format are:

-C
Write multi-text-column output with entries sorted down the columns, according to the collating sequence. The number of text columns and the column separator characters are unspecified, but should be adapted to the nature of the output device. This option disables long format output.

-m
Stream output format; list pathnames across the page, separated by a <comma> character followed by a <space> character. Use a <newline> character as the list terminator and after the separator sequence when there is not room on a line for the next list entry. This option disables long format output.

-x
The same as -C, except that the multi-text-column output is produced with entries sorted across, rather than down, the columns. This option disables long format output.

GNU Documentation

From GNU ls manual:

‘-1’
‘--format=single-column’
List one file per line. This is the default for ls when standard output is not a terminal. See also the -b and -q options to suppress direct output of newline characters within a file name. [Emphasis added]

Examples

Let's create three files:

$ touch file{1..3}

When output goes to a terminal, GNU ls chooses to use a multi-column format:

$ ls
file1  file2  file3

When output goes to a pipeline, the POSIX spec requires that single-column is the default:

$ ls | cat
file1
file2
file3

The three exceptions which override the default single-column behavior are -m for comma-separated, -C for columns sorted down, and -x for columns sorted across:

$ ls -m | cat
file1, file2, file3
$ ls -C | cat
file1  file2  file3
$ ls -x | cat
file1  file2  file3
Kusalananda
  • 333,661
John1024
  • 74,655
9
  • Why does piping the standard output change the behavior of ls?  Because it was designed that way.  The POSIX Specification says:

    The default format shall be to list one entry per line to standard output; the exceptions are to terminals or when one of the -C, -m, or -x options is specified.  If the output is to a terminal, the format is implementation-defined.

    which is actually ambiguous about the default behavior (when not specified by an option like -l or -1) with output to a terminal, and the GNU Coreutils documentation says

    If standard output is a terminal, the output is in columns (sorted vertically) and control characters are output as question marks; otherwise, the output is listed one per line and control characters are output as-is.

    So you can see that output to a file will act the same as output to a pipe; that is, one entry per line, as if -1 had been specified.

  • Why was it designed that way?  It might not be possible to know for sure (unless somebody can find some design notes), but I guess:
    • When ls is writing to a terminal, it expects that a human being is looking at the output.  People will prefer to get information in the minimum necessary number of lines, so stuff doesn’t scroll of the screen.
    • When ls is writing to a pipe, it expects that another program is reading the output.  It’s much easier for a program to read data that’s one value per line than to have to try to parse columns (since filenames can contain spaces).
  • Is there ever a reason to prefer ls -1 when you’re writing to a file or a pipe?  No.
-4

When piping ls, ls cannot determine how much columns the console actually has (independant of the right-side command). So ls just does that on its own choice, or, in other words, this behaviour is unstable and may change in future versions.

In contrast, ls -1 was created for the purpose of counting or scripting in general, so its behaviour is stable.

caylee
  • 251