5

I'm having trouble forming a bash array from a standard output. I've boiled it down to this minimal example:

~$ a=($(echo '1 2 3 "foo bar"'))
~$ echo ${a[0]}
1
~$ echo ${a[1]}
2
~$ echo ${a[2]}
3
~$ echo ${a[3]}
"foo
~$ echo ${a[4]}
bar"

I believe what is happening is that "foo and bar" are considered separate items in the standard output, but the goal would be to consolidate those items into one for the array.

Obviously, I could write a small loop to consolidate these terms into one, but I'm wondering of there is a more elegant solution?

EDIT: What goes in place of echo '1 2 3 "foo bar"' in my code is fairly convoluted, but the point is that I need to form an array from some unknown standard output of this form.

3
  • 1
    This is not going to happen without a serious kludge or two. Would you be okay with changing the output format? If you had each array entry on a separate line, that'd be a lot easier. Commented Mar 6, 2015 at 15:07
  • 1
    eval a=($(echo '1 2 3 "foo bar"')) Commented Mar 6, 2015 at 15:17
  • You can't do it without eval which is dangerous unless you can absolutely guarantee what your input will be (as it is a giant security hole otherwise). Commented Mar 6, 2015 at 15:31

3 Answers 3

7

xargs recognizes quotes so

mapfile -t a <<<"$(echo '1 2 3  "foo bar"' | xargs -n 1)"
printf "%s\n" "${a[@]}"
1
2
3
foo bar
Sign up to request clarification or add additional context in comments.

5 Comments

Simply GREAT! +10 if i could :)
Very nice - had no idea that xargs recognizes quoted tokens embedded in a string literal (works with both single and double quotes). For bash 3.x users (e.g., on OSX): IFS=$'\n' read -d '' -ra a <<<"$(echo '1 2 3 "foo bar"' | xargs -n 1)".
mapfile bash 4+ only. Otherwise -- FAB
Is there a reason to use the <<< form (or the < <(command) process substitution based form) rather than the simpler (reduced nested punctuation and reduced execution complexity), subshell-free approach of echo '1 2 3 "foo bar"' | xargs -n 1 | mapfile -t a? I can see the <<</< <() forms as being an alternative for when you want the definition of the variable on the left, command on the right, but the effect should be identical, right?
Never mind, answered my own question. I thought echo '1 2 3 "foo bar"' | xargs -n 1 | mapfile -t a worked but it was only failing and leaving a unchanged; I guess mapfile must be backed by real file system file (which <<< and < <() provide, with my system creating temp files for both <<< and <() that are then mapped onto fd 0, though the < <() obscures it with ls -l /proc/#####/fd showing it as a pipe, not a file, due to piping from it).
4

The process substitution is not only unnecessary, it is directly harmful. You want

a=(1 2 3 "foo bar")

If you know how many items to expect, you can do

read a b c d <<<$(echo '1 2 3 "foo bar"')

Eventually, I guess you can't escape eval.

2 Comments

I understand how this would make an array with the correct terms, but what goes in place of the echo '1 2 3 "foo bar"' is the output of a much more complex command. I'm not sure that this solves the issue, as I need to create an array from the standard output.
To give some context, now that the OP has clarified his intent: (a) The first snippet works only for creating arrays with a known number of elements (from literals or element-specific expansions). (b) The 2nd snippet works only if there's 1 quoted token at the end of the captured output (and that token will have its leading and trailing whitespace trimmed). (c) 1_CR's answer provides a generic solution based on xarg's ability to recognize quoted tokens inside string literals.
1

You could replace the

some unknown standard output of this form

into lines and read the lines into array by the mapfile command.

For example, this can be done by perl and it's core module like:

some_command() { echo '1 2 3 "foo bar"'; }

echo "output from some_command"
some_command

echo
echo "Parsed into array"
mapfile -t array < <(some_command | perl -MText::ParseWords -lnE 'say for shellwords($_)')
printf '=%s=\n' "${array[@]}"

what prints

output from some_command
1 2 3 "foo bar"

Parsed into array
=1=
=2=
=3=
=foo bar=

EDIT: Just recognised 1_CR's answer.

mapfile -t array < <(some_command | xargs -n 1)

is much better ;)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.