6

Say I have a list/array:

list=(a b c)

how can I echo each element to xargs? Something like:

for v in list; do echo v; done; | xargs

is there a less verbose way?

  • This needs a bit more context. How is the list being generated? – Brian Redbeard Sep 26 '18 at 02:36
  • list=(a b c) ; for v in "${list[@]}"; do printf "$v\n" >> file ; done ; cat file – abc Dec 14 '18 at 12:24
  • list=(a b c) ; for i in "${list[@]}" ; do echo "$i" | xargs -t espeak ; done – abc Dec 14 '18 at 12:39
  • 2
    Why was this marked as a duplicate? Although the solution is similar and can be adapted, the question is not the same, and may be answered differently. – Amir Dec 27 '18 at 09:53

2 Answers2

11
printf '%s\n' "${list[@]}" | xargs

This would print each element of list on its own line and that newline-delimited list would be passed to xargs.

"${list[@]}" would expand to the individually double quoted elements of list. printf will reuse its formatting string if given more arguments than there are placeholders in the formatting string.

That only works however if the elements don't contain whitespace nor single quotes nor double quotes nor backslash characters, are not the empty string (and with some xargs implementations are only made of text and are not _ and are relatively short).

For arbitrary (not too long¹) elements (that don't contain NUL characters, but current versions of bash can't store NULs in their variables anyway), you could pass the list as NUL-delimited records as supported by most xargs implementations with the -0 option (will be in the next version of the POSIX standard):

{
  [ "${#list[@]}" -eq 0 ] || printf '%s\0' "${list[@]}"
} | xargs

Using a print0 helper function may help:

print0() {
  [ "$#" -eq 0 ] || printf '%s\0' "$@"
}
print0 "${list[@]}" | xargs -0

Note that for the empty list, except on NetBSD, the command (echo by default) will still be invoked once without argument. That can be avoided with the -r option (also in the next version of the POSIX standard).


¹ while xargs is meant to work around the limit of the execve() system call, it won't cut single arguments in the middle of them, so if a single argument is larger than that limit, execve() won't be able to execute the command. Linux also has a limit on the size of individual arguments, separate from the overall limit on the cumulative size of all the arguments and environment variables that is common to most systems.

Kusalananda
  • 333,661
4

Unless it's a particularly long list you may be able to dispense with xargs entirely

list=(a b c)

With xargs

printf "%s\n" "${list[@]}" | xargs foo # Results in « foo a b c »

Without xargs

foo "${list[@]}" # Also results in « foo a b c »

Of course, the applicability of this to your situation is dependent on the additional flags (if any) that you want to pass to your actual xargs command.

If you want a foo to test this with, run this script and replace all instances above of foo with ./foo:

cat <<'EOF' > foo
#!/bin/bash
echo "Got $# arg(s): $*"
for ARG in "$@"; do echo "> $ARG <"; done
EOF
chmod a+x foo

Note also that by default (and as shown here), xargs splits its arguments on whitespace, so:

list=('a b' c d)                          # Three arguments; the first contains a space
printf "%s\n" "${list[@]}" | xargs foo    # Gives foo four arguments « a b c d »
foo "${list[@]}"                          # Gives foo three arguments, with the first containing a space, « 'a b' c d »
Chris Davies
  • 116,213
  • 16
  • 160
  • 287