I have very simple directory with "directory1" and "file2" in it. After
out=`ls`
I want to print my variable: echo $out
gives:
directory1 file2
but echo "$out"
gives:
directory1
file2
so using quotes gives me output with each record on separate line. As we know ls
command prints output using single line for all files/dirs (if line is big enough to contain output) so I expected that using double quotes prevents my shell from splitting words to separate lines while ommitting quotes would split them.
Pls tell me: why using quotes (used for prevent word-splitting) suddenly splits output ?
ls
ls
only prints multiple filenames on a single line by default when output is to a TTY. When output is to a pipeline, a file, or similar, then the default is to print one line to a file.
Quoting from the POSIX standard for ls
, with emphasis added:
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.
It's the very act of splitting your command into separate arguments that causes it to be put on one line! Natively, your value spans multiple lines, so echoing it unmodified (without any splitting) prints it precisely that manner.
The result of your command is something like:
out='directory1
file2'
When you run echo "$out"
, that exact content is printed. When you run echo $out
, by contrast, the behavior is akin to:
echo "directory1" "file2"
...in that the string is split into two elements, each passed as completely different argument to echo
, for echo
to deal with as it sees fit -- in this case, printing both those arguments on the same line.
Word-splitting may look like it does what you want here, but that's often not the case! Consider some particular issues:
*
surrounded by whitespace, that *
will be replaced with a list of files in the current directory, leading to duplicate results.See Why you shouldn't parse the output of ls
. In short -- in your example of out=`ls`
, the out
variable (being a string) isn't able to store all possible filenames in a useful, parsable manner.
Consider, for instance, a file created as such:
touch $'hello\nworld"three words here"'
...that filename contains spaces and newlines, and word-splitting won't correctly detect it as a single name in the output from ls
. However, you can store and process it in an array:
# create an array of filenames
names=( * )
if ! [[ -e $names || -L $names ]]; then # this tests only the FIRST name
echo "No names matched" >&2 # ...but that's good enough.
else
echo "Found ${#files[@]} files" # print number of filenames
printf '- %q\n' "${names[@]}"
fi