2

I have the following example:

$ a="$(ls)"
$ echo $a
backups cache crash lib local lock log mail opt run snap spool tmp
$
$ echo "$a"
backups
cache
crash
lib
local
lock
log
mail
opt
run
snap
spool
tmp

Now with printf:

$ printf $a
backups
$
$ printf "$a"
backups
cache
crash
lib
local
lock
log
mail
opt
run
snap
spool
tmp

Why is the output so different? What do quotes do in this situation? Could someone explain what's going on here?

P.S. Found some explanation on the ls behavior:
Output from ls has newlines but displays on a single line. Why?
https://superuser.com/questions/424246/what-is-the-magic-separator-between-filenames-in-ls-output
http://mywiki.wooledge.org/ParsingLs
The newline characters can be checked this way:

ls | od -c
t7e
  • 293
  • 1
  • 6
  • 2
    The fact that `ls` outputs differently to a terminal vs. to a non-terminal is different from the issue of word-splitting that you're seeing with `echo $a` vs. `echo "$a"`. You could change that command substitution to `a=$(printf 'foo\nbar\n')` (or `a=$(seq 3)`, or...) and you'd get the same issue wrt. the quoted vs. unquoted expansion. – ilkkachu Jun 22 '22 at 09:30
  • 1
    obligatory links: http://mywiki.wooledge.org/WordSplitting and [Why does my shell script choke on whitespace or other special characters?](https://unix.stackexchange.com/questions/131766/why-does-my-shell-script-choke-on-whitespace-or-other-special-characters) and [When is double-quoting necessary?](https://unix.stackexchange.com/questions/68694/when-is-double-quoting-necessary) – ilkkachu Jun 22 '22 at 09:31
  • 1
    Also ["I just assigned a variable, but `echo $variable` shows something else"](https://stackoverflow.com/questions/29378566/i-just-assigned-a-variable-but-echo-variable-shows-something-else). – Gordon Davisson Jun 22 '22 at 09:44

2 Answers2

6

echo $a is the same as

echo backups cache crash lib local lock log mail opt run snap spool tmp

whereas echo "$a" is the same as

echo 'backups
cache
crash
lib
local
lock
log
mail
opt
run
snap
spool
tmp'

See https://mywiki.wooledge.org/Quotes.

The first argument for printf is a formatting string and printf $a is the same as printf backups cache crash lib local lock log mail opt run snap spool tmp so it's using the string backups as the format and discarding the rest since there's nothing like %s in the formatting string to use them in. Just like:

$ printf foo whatever
foo$

$ printf '%s\n' foo whatever
foo
whatever

Don't do a="$(ls)" to try to create a scalar variable holding file names btw as that's fragile, do a=(*) to hold them in an array instead.

Ed Morton
  • 28,789
  • 5
  • 20
  • 47
  • Ok, what about the crippled `echo` in my example? – t7e Jun 21 '22 at 23:05
  • echo's a different command, one that doesnt take a formatting string so it's just printing whatever you pass to it as arguments. – Ed Morton Jun 21 '22 at 23:06
  • I mean the output with quoting and without is different. Also, `a=(*)` does not list all files. – t7e Jun 21 '22 at 23:07
  • 1
    Yes it does, do `declare -p a` to see them. I updated my answer. – Ed Morton Jun 21 '22 at 23:08
  • Could you please elaborate on the difference between `echo $a` and `echo "$a"` ? I don't get it. It is not the same as single quotes vs double quotes. – t7e Jun 21 '22 at 23:13
  • 1
    Just read the reference article I provide, it explains quotes very well. – Ed Morton Jun 21 '22 at 23:14
  • 2
    @t7e In short, `echo` outputs its arguments with spaces between them. This is what `echo` does. When using `echo $a`, the shell splits the contents of `$a` into several arguments based on spaces, tabs and newlines (and then also does filename globbing on each generated word). `echo` then prints them with spaces between them. With `echo "$a"`, you only ever give `echo` a single argument. The string `"$a"` contains newlines from the output of `ls`, and these are retained and outputted by `echo`. – Kusalananda Jun 22 '22 at 04:41
  • Thanks, @Kusalananda. Could you leave it as an answer instead of a comment? I have one more question then, `ls` by itself outputs files on one line like does `echo $a` (without qoutes), so where does `echo "$a"` find the new line characters? – t7e Jun 22 '22 at 07:30
  • @t7e I've already mentioned that `"$a"` contains the newlines outputted by `ls`. I will not write a new answer for you about this because Ed's answer here is already complete. What my comment did was merely to chew the contents of Greg's wiki for you, which Ed linked to. – Kusalananda Jun 22 '22 at 07:54
  • Well, that article does not give the explanation why `ls` and `ls -1` give different output. I found some reference - will add in my question. – t7e Jun 22 '22 at 08:33
  • @t7e why `ls` and `ls -1` give different output have absolutely nothing at all to do with your original question. Please put your question back as it was when you got answers and ask a new question if you want to know the difference between `ls` and `ls -1`. – Ed Morton Jun 22 '22 at 10:57
  • @Ed Morton the question is connected with it because I could not figure out where `echo` takes the new lines from in your example. Please check my own answer to this question. – t7e Jun 22 '22 at 11:27
1

Thanks to @Ed Morton and @Kusalananda for the explanation.
I guess my problem was that I always thought that, by default, ls splits the files using spaces or tabs. But in fact, it turned out that it separates them with new line characters but outputs the files in columns (sorted vertically) when printing to a terminal. Newline characters can be checked with:

ls | od -c

I'll move the @Kusalananda's answer from the comment section to an answer, since it was helpful:

In short, echo outputs its arguments with spaces between them. This is what echo does.
When using echo $a, the shell splits the contents of $a into several arguments based on spaces, tabs and newlines (and then also does filename globbing on each generated word). echo then prints them with spaces between them.
With echo "$a", you only ever give echo a single argument. The string "$a" contains newlines from the output of ls, and these are retained and outputted by echo.

t7e
  • 293
  • 1
  • 6
  • 2
    "it separates them with new line characters but outputs the files in columns (sorted vertically)" -- The output of `ls` depends on if it prints to a terminal or to something else. – Kamil Maciorowski Jun 22 '22 at 09:36
  • to see the output with multiple filenames per line, you'd need to capture the output without it going somewhere other than a terminal. Something like running it under `script`, or `ssh -t somehost ls > ls.txt`, or maybe look at the system calls with `strace`, though it abbreviates long string arguments. – ilkkachu Jun 22 '22 at 09:47
  • 1
    If you're interested in `ls`, see https://mywiki.wooledge.org/ParsingLs – Ed Morton Jun 22 '22 at 11:30