3
set +f; rm *; touch a; for i in *; do touch b; echo $i; done

在我尝试过的所有 shell(dash、ksh、zsh、bash)中,上面的代码片段只输出“a”。在 C 中实现相同的(opendir/loop on readdir 创建文件)也只输出“a”。但是,如果目录包含足够多的文件(~4096),C 实现通常也会输出“b”。(即,readdir 返回在 opendir 之后创建的文件的结果)。我在 shell 标准中看不到任何东西表明 shell 在这种情况下应该如何响应。一个符合标准的 shell 可以进入 glob 之后创建的文件的循环吗?这将是一个非常理想的特性,因为这意味着 shell 在执行任何操作之前不会将整个 glob 读入内存。在预期目录包含许多文件的情况下,通常需要几秒钟才能将 glob 读入内存,

是否有任何 shell 实现在进入循环之前不会将整个 glob 读入内存?

4

1 回答 1

3

不。 glob 扩展的上下文与普通的命令扩展上下文基本相同,其中所有扩展都被处理,生成的单词以不可变的方式保存以进行迭代。for-in 循环没有惰性迭代器。当然,扩展可能会产生副作用并与 glob 混合,因此必须热切地评估它们。这就是为什么find -exec [+;]在可以同时做事情的情况下仍然如此频繁地推荐使用 globstar。

关于这个 4096 问题,我真的无话可说。我觉得这两者真的没有可比性。Shell for..in 只是扩展单词并迭代它们。

一个相关的常见问题解答是您是否可以执行诸如提前读取要分配的下一个值之类的操作。据我所知,没有类似 bourne 的 shell 可以额外访问单词列表。您必须为此使用数组。基本上所有的限制for..in都可以通过数组来克服。

这是我为 Bash 编写的一个有趣的惰性 coproc 生成器。这很没用。

coproc x { while :; do find . -type f -maxdepth 1 -exec sh -c 'read; echo "$1"' -- {} \;; done; };

while :; do
    echo 1 >&"${x[1]}"
    read -ru "${x[0]}" file
    echo "$file"
    sleep 1
done

还有一个for..in与问题无关的花絮——在 ksh93 和 Bash 的 git devel 分支中,可以以一种有趣的方式使用“控制变量”。

function f {
    nameref x # Chet may decide not to emulate the typeset -n aliases

    for x; do
        x=hi
    done
}

typeset -a arr
f 'arr['{0..3}']'
typeset -p arr # arr=(hi hi hi hi)

每次迭代都会将给定对象的引用分配给 x。当然,在 ksh 中,它可以是任意复杂的数据类型。我想这可能会被滥用以某种方式模拟懒惰。不幸的是,这种模式似乎不适用于 mksh。

编辑忘记写这篇文章后,我发现很多 shell 实际上优化了for x语法。我假设至少是写时复制,并且仅在循环中使用或for x in时复制位置参数。shiftset

于 2012-10-15T14:18:42.817 回答