6

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?

4

5 回答 5

7

由于您正在读取一个复杂的命令管道,因此您可以使用以下命令:

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

该语法<(command)在子 shell 中运行命令并将其标准输出作为临时文件打开。您可以在通常在命令中使用文件的任何地方使用它。

此外,您还可以使用语法>(command)将标准输入作为文件打开。

于 2013-10-03T16:26:52.157 回答
3

如果您使用的是bash4.2,则可以设置lastpipeshell 选项以允许 while 循环作为管道中的最后一个元素,在当前 shell 而不是子 shell 中运行。

一个简单的演示:

$ 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
于 2013-10-03T17:26:09.587 回答
2

有没有一种合法的方法来更新循环内的关联数组形式,或者一般的子shell?

您可以通过以下方式避免子外壳:

while read f; do
  ...
done < *.txt

也就是说,您的示例代码有问题。循环会逐行读取文件,所以说

wordcounts[$f]=$(wc -w  $f)

真的没有多大意义。你可能想说:

wordcounts[$f]=$(wc -w <<< $f)

编辑:

唉,我似乎需要管道......

引用手册

管道中的每个命令都在其自己的子 shell 中执行(请参阅命令执行环境)。

于 2013-10-03T16:01:25.227 回答
0

为什么ls不必要地使用。

以下工作正常:

declare -a wordcounts
for f in *.txt; do
    wordcounts+=$(wc -w  $f)
done
echo ${wordcounts[@]} 
于 2013-10-03T15:54:02.727 回答
0

我认为最好的解决方案是 Cookyt 的解决方案:

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

对我来说,这不起作用,因为在我的环境中我没有/proc安装,因为<(cmd)构造需要/dev/fd/XXX并且/dev/fd/proc/self/fd. 在这些情况下,由 chepner 提出的解决方案有效:

shopt -s lastpipe
my | complex | command | pipe | while read f; do
    # Do stuff
done

如果您也没有 bash,还有第三种解决方案适用于 POSIX shell(因此也适用于 bash):

set -- $(my | complex | command | pipe)
while [ -n "$1" ]; do
    f="$1"
    shift
    # Do stuff
done
于 2020-11-27T11:22:03.747 回答