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.

mklement0
  • 382,024
  • 64
  • 607
  • 775
alexoneill
  • 503
  • 3
  • 13
  • 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. – John Kugelman Mar 06 '15 at 15:07
  • 1
    `eval a=($(echo '1 2 3 "foo bar"'))` – iruvar Mar 06 '15 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). – Etan Reisner Mar 06 '15 at 15:31

3 Answers3

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
iruvar
  • 22,736
  • 7
  • 53
  • 82
  • 1
    Simply GREAT! +10 if i could :) – clt60 Mar 06 '15 at 15:57
  • 2
    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)"`. – mklement0 Mar 06 '15 at 16:28
  • `mapfile` bash 4+ only. Otherwise -- FAB – dawg Mar 07 '15 at 06:16
  • 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? – ShadowRanger Jan 03 '19 at 17:30
  • 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). – ShadowRanger Jan 03 '19 at 17:43
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.

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • 4
    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. – alexoneill Mar 06 '15 at 15:09
  • 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](http://stackoverflow.com/a/28902380/45375) answer provides a _generic solution_ based on `xarg`'s ability to recognize quoted tokens inside string literals. – mklement0 Mar 06 '15 at 16:36
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 ;)

clt60
  • 62,119
  • 17
  • 107
  • 194