2

I'm wondering why this code is not working as expected:

a=(1)
echo "1 2 3 4" | while read f; do
    a+=("$f")
done
echo "${a[@]}"

Output is "1" and not "1 1 2 3 4" as I thought it would be. Any ideas?

codeforester
  • 39,467
  • 16
  • 112
  • 140
flix
  • 85
  • 7
  • ... and https://stackoverflow.com/questions/6572411/cant-use-a-variable-out-of-while-and-pipe-in-bash – melpomene Nov 26 '17 at 23:18
  • 2
    your while loop is redundant, there is only one value, which is "1 2 3 4". Replace array concat with `echo $f` to verify – karakfa Nov 26 '17 at 23:20

1 Answers1

2

If you create a pipe, you create a sub shell. The a array is correct modified, but only in the sub shell which exits before you print the contents of a after the loop.

Solution, use process substitution or a here string instead of a pipe:

a=(1)
# this only loops once since there is only one line...
while read f; do    
    a+=("$f")
done < <(echo "1 2 3 4")    # < <(process substitution)
echo "${a[@]}"
# 1 1 2 3 4

As a side note, use declare -p a instead of echo "${a[@]}" to see the contents of an array in Bash if you also want to see the indices.

Here is a better example of what you are seeing:

#!/bin/bash
lines=("Top")
echo "Line 1
Line 2
Line 3" | while read -r line; do
    lines+=("$line")
    declare -p lines
done 
declare -p lines

Prints:

declare -a lines=([0]="Top" [1]="Line 1")
declare -a lines=([0]="Top" [1]="Line 1" [2]="Line 2")
declare -a lines=([0]="Top" [1]="Line 1" [2]="Line 2" [3]="Line 3")
declare -a lines=([0]="Top")

Note that lines reverts to only one entry after the pipeline. Vs:

lines=("Top")
while read -r line; do
    lines+=("$line")
    declare -p lines
done < <(echo "Line 1
Line 2
Line 3")
declare -p lines

Prints:

declare -a lines=([0]="Top" [1]="Line 1")
declare -a lines=([0]="Top" [1]="Line 1" [2]="Line 2")
declare -a lines=([0]="Top" [1]="Line 1" [2]="Line 2" [3]="Line 3")
declare -a lines=([0]="Top" [1]="Line 1" [2]="Line 2" [3]="Line 3")
dawg
  • 98,345
  • 23
  • 131
  • 206