it's not that "printf can't handle the += operator", it's that you're using printf
wrong, because bash doesn't expand arrays like you assume it does.
printf "in while(printf): array=${array[@]}\n"
is NOT expanded by bash as:
printf "in while(printf): a _b c d\n"`
It is expanded as:
printf "in while(printf): a _b\n" "c" "d"
The first element of the array is expanded inside the double-quoted string. The remainder are expanded as additional arguments outside of the string. Since you don't have any %s
format strings ("conversion specifiers") for the args "c"
and "d"
, they're not printed.
To do what you want, try something like this:
printf "in while(printf): array="
printf "%s " "${array[@]}"
printf "\n"
But note that there will be a trailing space in the output after the last element of the array. To avoid, that, and a better solution all-round is to use a proper join function to join the elements of the array into a single string. Unfortunately, bash doesn't have one built-in, but it's not too difficult to write one. e.g.
function join_by {
[ -z "$1" ] && return
local d="$1"; shift; # delimiter AKA separator
[ -z "$1" ] && return
printf "$1"; shift; # first arg after delimiter
printf "%s" "${@/#/$d}"; # remaining args, if any
}
j=$(join_by " " "${array[@]}")
printf "in while(printf): $j\n"
"cannot get results of += out of while loop".
That's because the while loop is being run in a pipeline, which means that it's being run in a separate child shell. Child processes CAN NOT affect the environment of their parent process. This means that any changes you make to the array only happen within the child process, i.e. they're ephemeral and disappear when the child exits.
You can avoid this by using Process Substition instead of a pipe to feed data into the while read
loop. That way, the while read
is being run in the main shell, not in a child shell. For example:
#!/bin/bash
function join_by {
[ -z "$1" ] && return
local d="$1"; shift; # delimiter AKA separator
[ -z "$1" ] && return
printf "$1"; shift; # first arg after delimiter
printf "%s" "${@/#/$d}"; # remaining args, if any
}
declare -a array=("a b")
while IFS= read -r line; do
array+=($line)
echo "in while(echo): array=${array[@]}"
j=$(join_by " " "${array[@]}")
printf "in while(printf): array=$j\n"
echo; echo
done < <(printf "%s\n" c d)
echo "out of while(echo): array=${array[@]}"
j=$(join_by " " "${array[@]}")
printf "out of while(printf): array=$j\n"
Example output:
in while(echo): array=a b c
in while(printf): array=a b c
in while(echo): array=a b c d
in while(printf): array=a b c d
out of while(echo): array=a b c d
out of while(printf): array=a b c d
Rather than a while read loop, you're probably better off just using readarray
. For example:
array=("a b")
typeset -p array
readarray -t tmp_array < <(printf "%s\n" c d)
typeset -p tmp_array
array+=("${tmp_array[@]}")
typeset -p array
Output:
declare -a array=([0]="a b")
declare -a tmp_array=([0]="c" [1]="d")
declare -a array=([0]="a b" [1]="c" [2]="d")