33

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?

5
  • 1
    because you are doing echo. The output of ls as input to echo command.
    – ankidaemon
    May 17 '16 at 7:53
  • This is "expected" behaviour, which someone will explain shortly. However, are you sure that ls by itself doesn't give you multiple items per line?
    – roaima
    May 17 '16 at 7:59
  • @roaima columnized ls is a remnant of a bsd "feature". unix versions print one entry per line
    – Steve Cox
    May 17 '16 at 14:41
  • @SteveCox I haven't used proper UNIX since early SVR4 so my memory's a little hazy. Thank you for the reminder.
    – roaima
    May 17 '16 at 16:11
  • Why not echo *?
    – jdh8
    Oct 3 '16 at 8:18
77

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
8
  • 4
    additionally, using isatty you can check if stdout is a tty, ls uses this to determine whether to use colors or not. May 17 '16 at 9:18
  • 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
  • 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
0

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.