39

Consider the two shell samples

$ ls
myDoc.html
SomeDirectory
someDoc.txt

and

$ echo $(ls)
myDoc.html SomeDirectory someDoc.txt

The first executes ls which, as I understand, appends the contents of the current working directory to the stdout file (which is what the terminal displays). Is this correct?

The second takes the value of the ls command (which means is the contents of the current working directory) and prints it to the stdout file. Is this correct?

Why do the two commands give different outputs?

Luciano
  • 1,159
Owen
  • 569
  • 1
  • 5
  • 8

2 Answers2

84

When you run this command:

ls

the terminal displays the output of ls.

When you run this command:

echo $(ls)

the shell captures the output of $(ls) and performs word splitting on it. With the default IFS, this means that all sequences of white space, including newline characters, are replaced by a single blank. That is why the output of echo $(ls) appears on one line.

For an advanced discussion of word splitting, see Greg's FAQ.

Suppressing word splitting

The shell does not perform word splitting on strings in quotes. Thus, you can suppress word splitting and retain the multiline output with:

echo "$(ls)"

ls and multiline output

You may have noticed that ls sometimes prints more than one file per line:

$ ls
file1  file2  file3  file4  file5  file6

This is the default when the output of ls goes to a terminal. When the output is not going directly to a terminal, ls changes its default to one file per line:

$ echo "$(ls)"
file1
file2
file3
file4
file5
file6

This behavior is documented in man ls.

Another subtlety: command substitution and trailing newlines

$(...) is command substitution and the shell removes trailing newline characters from output of command substitution. This normally is not noticeable because, by default, echo adds one newline to the end of its output. So, if you lose one newline from the end of $(...) and you gain one from echo, there is no change. If, however, the output of your command ends with 2 or more newline characters while echo adds back only one, your output will be missing one or more newlines. As an example, we can use printf to generate trailing newline characters. Note that both of the following commands, despite the different number of newlines, produce the same output of one blank line:

$ echo "$(printf "\n")"

$ echo "$(printf "\n\n\n\n\n")"

$ 

This behavior is documented in man bash.

Another surprise: pathname expansion, twice

Let's create three files:

$ touch 'file?' file1 file2

Observe the difference between ls file? and echo $(ls file?):

$ ls file?
file?  file1  file2
$ echo $(ls file?)
file? file1 file2 file1 file2

In the case of echo $(ls file?), the file glob file? is expanded twice, causing the file names file1 and file2 to appear twice in the output. This is because, as Jeffiekins points out, pathname expansion is performed first by the shell before ls is run and then again before echo is run.

The second pathname expansion can be suppressed if we used double-quotes:

$ echo "$(ls file?)"
file?
file1
file2
John1024
  • 74,655
  • 4
    additionally, using isatty you can check if stdout is a tty, ls uses this to determine whether to use colors or not. – Janus Troelsen May 17 '16 at 09:18
  • @JanusTroelsen: bash also has the -t test operator for that. (from man page: -t fd True if file descriptor fd is open and refers to a terminal.) – 0xC0000022L May 17 '16 at 12:19
  • 1
    Aren't the quotes mismatched in the last code snippet? Or do $( ) actually provide a syntax barrier so you needn't interpolate? – cat May 17 '16 at 13:09
  • 6
    @cat $() provides a syntax barrier. – Random832 May 17 '16 at 14:29
  • 2
    One more important difference: if any filename contains a wildcard character, the echo command will expand the wildcard. For example, if ls returns file? file1 file2, then echo $(ls) will return file? file1 file2 file1 file2. – Jeffiekins May 17 '16 at 21:42
  • @Jeffiekins Interesting suggestion. I just added a discussion of that to the answer. – John1024 May 18 '16 at 05:43
  • 1
    @Jeffiekins echo does not expand wildcards; the shell does before passing the resulting expansion as arguments to echo. – chepner May 18 '16 at 16:35
  • @chepner You are, of course, correct. I just figured, from the question, that exactly who does the expansion was more likely to confuse than to clarify. – Jeffiekins May 22 '16 at 18:34
1

ls is aware if it is sending output to a terminal or not.

Executing ls at a command prompt will write to your pseudo-tty. Redirecting the output of ls will generally not be going to a pseudo-tty, and ls will format the output differently.

mpez0
  • 228
  • 1
  • 4