Reputation: 40894
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?
Upvotes: 7
Views: 3192
Reputation: 7164
I think the best solution is the one by Cookyt:
while read f; do
# Do stuff
done < <(my | complex | command | pipe)
For me, that didn't work because in my environment I don't have /proc
mounted because the <(cmd)
construct needs /dev/fd/XXX
and /dev/fd
is a symlink to /proc/self/fd
. In those cases, the solution by by chepner works:
shopt -s lastpipe
my | complex | command | pipe | while read f; do
# Do stuff
done
If you also don't have bash, there is a third solution that works with POSIX shells (and thus also with bash):
set -- $(my | complex | command | pipe)
while [ -n "$1" ]; do
f="$1"
shift
# Do stuff
done
Upvotes: 0
Reputation: 531625
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
Upvotes: 3
Reputation: 1607
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.
Upvotes: 7
Reputation: 123588
Is there a legitimate way to update an associative array form within a loop, or a subshell in general?
You could avoid a subshell by saying:
while read f; do
...
done < *.txt
That said, you sample code has problems otherwise. The loop would read the file line by line, so saying
wordcounts[$f]=$(wc -w $f)
wouldn't really make much sense. You probably wanted to say:
wordcounts[$f]=$(wc -w <<< $f)
EDIT:
Alas, I seem to need the pipe ...
Quoting from the manual:
Each command in a pipeline is executed in its own subshell (see Command Execution Environment).
Upvotes: 2