Double quotes only work when they are literally present in the outermost command being run. If you include some string expression that just contains double quotes, those are just ordinary characters in the value as far as the shell is concerned.
While this sees two arguments:
set -- "first word" "second word"; echo $#
#=> 2
This sees four:
set -- $(echo "first word" "second word"); echo $#
#=> 4
And this sees only one:
set -- "$(echo "first word" "second word")"; echo $#
#=> 1
And making the echo
output literal quotation marks doesn't change any of that; you just get quotation marks in your parameters:
set -- $(echo '"first word" "second word"')
echo $#
#=> 4
echo $1
#=> "first
set -- "$(echo '"first word" "second word"')"
echo $#
#=> 1
echo $1
#=> "first word" "second word"
The upshot is that if you want the output of a command to create words that are easily separable by the shell, you should not output double-quoted strings; that's going to require an extra level of evaluation on the output, which is possible but opens you up to all sorts of potential mischief by the users running your script. Instead, have the command output plain strings, with no quotation marks around them, but separated by some character that doesn't appear inside any of the individual elements. A newline is great if none of your strings contain newlines:
$ ./a.out
Calibre Library
VirtualBox VMs
But you can use any convenient character that doesn't appear in any of the values; NUL (character code 0, usually represented in strings as "\0") is a popular choice because it's almost never needed inside a string and often isn't even allowed, depending on the program you're passing it to.
Once you have a command that does that, you can do a couple of things. If all you want is to turn its output into arguments that get passed to another command directly, you can use xargs
:
./a.out | xargs ls
If it outputs NUL-separated strings instead of newline-separated ones, use the -0 parameter:
./a.out | xargs -0 ls
More generally, if you want to keep a list of strings around in the shell, you should store them in an array. You can set one to literal values like this:
files=("Calibre Library" "VirtualBox VMs")
Or you can use mapfile
to set it from the output of a command that prints out strings suitable for input to xargs
:
mapfile -t files < <(./a.out)
Or, if they're NUL-delimited:
IFS=$'\0' mapfile -t files < <(./a.out)
Then you can use the special expression "${files[@]}"
to get the individual elements of the array back out and pass them to another command:
ls "${filenames[@]}"
The ${arrayName[@]}
expression is somewhat magical in that the double-quotes around it are effectively inherited by each individual element of the array instead of just grouping the whole thing into one big word. (If you want it all as one big word, which is generally less useful, you can still get that by using [*]
instead of [@]
).