Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

bash: how to update an associative array in an implicit subshell?

The problem: I cannot update an array in a while loop. An illustration (not the actual problem):

declare -A wordcounts
wordcounts["sentinel"]=1000
ls *.txt | while read f; do
  # assume that that loop runs multiple times
  wordcounts[$f]=$(wc -w  $f)
  echo ${wordcounts[$f]}  # this prints actual data
done
echo ${!wordcounts[@]}  # only prints 'sentinel'

This does not work, because the loop after the pipe runs in a subshell. All the changes that the loop does to variable wordcounts are only visible inside the loop.

Saying export wordcounts does not help.

Alas, I seem to need the pipe and the while read part, so ways to rewrite the code above using for is not what I'm looking for.

Is there a legitimate way to update an associative array form within a loop, or a subshell in general?

like image 335
9000 Avatar asked Oct 03 '13 15:10

9000


Video Answer


2 Answers

Since you have a complex command pipe you're reading from, you can use the following:

while read f; do
    # Do stuff
done < <(my | complex | command | pipe)

The syntax <(command) runs a command in a subshell and opens its stdout as a temporary file. You can use it any place where you would normally use a file in a command.

Further, you can also use the syntax >(command) to open stdin as a file instead.

like image 155
Cookyt Avatar answered Sep 24 '22 08:09

Cookyt


If you are using bash 4.2, you can set the lastpipe shell option to allow the while loop, as the last element in the pipeline, to run in the current shell instead of a subshell.

A simple demonstration:

$ echo foo | read word
$ echo $word

$ set +m  # Only needed in an interactive shell to disable job control
$ shopt -s lastpipe
$ echo foo | read word
$ echo $word
foo
like image 21
chepner Avatar answered Sep 25 '22 08:09

chepner