3

Consider this example, say, test.sh:

cat > test.txt <<'EOF'
test 1
test 2
test 3
EOF

declare -a myarr
declare -p myarr  # "declare: myarr: not found"
myarr=()
declare -p myarr  # "declare -a myarr='()'"
#for (( i=1; i<=3; i++ )); do # ok
sed -n 's!test!TEST!p' test.txt | while read i; do # not preserved ?!
  myarr=("${myarr[@]}" "pass $i")
  declare -p myarr
done
declare -p myarr  # "declare -a myarr='()'" ?!

If I uncomment the for ((... line, and comment the sed -n ... line, then the output of bash test.sh is as expected:

test.sh: line 8: declare: myarr: not found
declare -a myarr='()'
declare -a myarr='([0]="pass 1")'
declare -a myarr='([0]="pass 1" [1]="pass 2")'
declare -a myarr='([0]="pass 1" [1]="pass 2" [2]="pass 3")'
declare -a myarr='([0]="pass 1" [1]="pass 2" [2]="pass 3")'

However, if I run the script as posted, then the myarr builds in the while loop, but once outside, it's empty:

test.sh: line 8: declare: myarr: not found
declare -a myarr='()'
declare -a myarr='([0]="pass TEST 1")'
declare -a myarr='([0]="pass TEST 1" [1]="pass TEST 2")'
declare -a myarr='([0]="pass TEST 1" [1]="pass TEST 2" [2]="pass TEST 3")'
declare -a myarr='()'

So, why is myarr in this case (in, or rather, after the while loop) empty - and how do I get it to preserve its value?

sdaau
  • 36,975
  • 46
  • 198
  • 278

1 Answers1

8

Problem is that due to use of pipeline, you are forking a subshell and populating entries in your array inside the subshell. Once your loop ends, subshell terminates and all the changes get lost there.

You can use a process substitution to avoid this:

myarr=()

while IFS= read  -r line; do
   myarr+=("pass $line")
done < <(sed -n 's!test!TEST!p' test.txt)

# examine the result
declare -p myarr

Output:

declare -a myarr=([0]="pass TEST 1" [1]="pass TEST 2" [2]="pass TEST 3")
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • 1
    Many thanks @anubhava -> that was indeed the issue! Cheers! – sdaau Nov 10 '17 at 05:18
  • I prefer using other forms such as `exec {fd}< <(sed ...); while read -r -u $fd line; do ...; done; exec {fd}<&-` because it prevents accidentally consuming the same source within the loop, but this is the right way to do it. – ephemient Nov 10 '17 at 05:30