动态通配符扩展

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 creating a file),也仅输出"a"。然而,如果目录包含足够多的文件(~4096),C实现通常也会输出"b"。(即readdir返回opendir后创建的文件的结果)。我没有看到任何shell标准表明shell应该如何在这种情况下做出响应。符合标准的shell是否可以进入glob之后创建的文件的循环?这将是一种非常理想的特性,因为它意味着shell在执行任何操作之前不会将整个glob读入内存。在预计目录包含许多文件的情况下,仅读取glob到内存中通常需要几秒钟,这是浪费时间的做法。
是否有任何shell实现不会在进入循环之前将整个glob读入内存?
1个回答

3
不可以。扩展的上下文与普通命令扩展上下文基本相同,其中所有扩展都会被处理并将生成的单词以不可变的方式保存以进行迭代。在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开发分支中,可以以有趣的方式利用“控制变量”。

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时才复制位置参数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接