此第一版为 Lua 5.0 编写。虽然在很大程度上仍然适用于后续版本,但存在一些差异。
第四版针对 Lua 5.3,可在 亚马逊 和其他书店购买。
购买本书,您还可以帮助 支持 Lua 项目


29.1 – 目录迭代器

之前,我们实现了一个 dir 函数,它返回一个表,其中包含给定目录中的所有文件。我们的新实现将返回一个迭代器,每次调用时都会返回一个新条目。借助此新实现,我们将能够使用如下所示的循环遍历目录

    for fname in dir(".") do  print(fname)  end

要在 C 中遍历目录,我们需要一个 DIR 结构。DIR 的实例由 opendir 创建,并且必须通过调用 closedir 显式释放。我们之前实现的 dir 将其 DIR 实例作为局部变量保留,并在检索到最后一个文件名后关闭该实例。我们的新实现无法将此 DIR 实例保留在局部变量中,因为它必须在多次调用中查询此值。此外,它不能仅在检索到最后一个名称后才关闭目录;如果程序中断循环,迭代器将永远无法检索到此最后一个名称。因此,为了确保始终释放 DIR 实例,我们将它的地址存储在用户数据中,并使用此用户数据的 __gc 元方法来释放目录结构。

尽管此表示目录的用户数据在我们实现中扮演着核心角色,但 Lua 不需要看到它。dir 函数返回一个迭代器函数;这是 Lua 所看到的。目录可能是迭代器函数的上值。因此,迭代器函数可以直接访问此结构,但 Lua 代码不能(也不需要)。

总之,我们需要三个 C 函数。首先,我们需要 dir 函数,这是一个工厂,Lua 调用它来创建迭代器;它必须打开一个 DIR 结构,并将其作为迭代器函数的上值。其次,我们需要迭代器函数。第三,我们需要 __gc 元方法,它关闭一个 DIR 结构。和往常一样,我们还需要一个额外的函数来进行初始安排,例如为目录创建一个元表并初始化此元表。

让我们从 dir 函数开始我们的代码

    #include <dirent.h>
    #include <errno.h>
    
    /* forward declaration for the iterator function */
    static int dir_iter (lua_State *L);
    
    static int l_dir (lua_State *L) {
      const char *path = luaL_checkstring(L, 1);
    
      /* create a userdatum to store a DIR address */
      DIR **d = (DIR **)lua_newuserdata(L, sizeof(DIR *));
    
      /* set its metatable */
      luaL_getmetatable(L, "LuaBook.dir");
      lua_setmetatable(L, -2);
    
      /* try to open the given directory */
      *d = opendir(path);
      if (*d == NULL)  /* error opening the directory? */
        luaL_error(L, "cannot open %s: %s", path,
                                            strerror(errno));
    
      /* creates and returns the iterator function
         (its sole upvalue, the directory userdatum,
         is already on the stack top */
      lua_pushcclosure(L, dir_iter, 1);
      return 1;
    }
这里有一个微妙之处,即我们必须在打开目录之前创建用户数据。如果我们首先打开目录,然后调用 lua_newuserdata 引发错误,我们将丢失 DIR 结构。按照正确的顺序,一旦创建 DIR 结构,它将立即与用户数据关联;无论之后发生什么,__gc 元方法最终都会释放该结构。

下一个函数是迭代器本身

    static int dir_iter (lua_State *L) {
      DIR *d = *(DIR **)lua_touserdata(L, lua_upvalueindex(1));
      struct dirent *entry;
      if ((entry = readdir(d)) != NULL) {
        lua_pushstring(L, entry->d_name);
        return 1;
      }
      else return 0;  /* no more values to return */
    }

__gc 元方法关闭一个目录,但它必须采取一个预防措施:因为我们在打开目录之前创建用户数据,所以无论 opendir 的结果如何,这个用户数据都将被收集。如果 opendir 失败,则将无事可关。

    static int dir_gc (lua_State *L) {
      DIR *d = *(DIR **)lua_touserdata(L, 1);
      if (d) closedir(d);
      return 0;
    }

最后,有一个打开这个单函数库的函数

    int luaopen_dir (lua_State *L) {
      luaL_newmetatable(L, "LuaBook.dir");
    
      /* set its __gc field */
      lua_pushstring(L, "__gc");
      lua_pushcfunction(L, dir_gc);
      lua_settable(L, -3);
    
      /* register the `dir' function */
      lua_pushcfunction(L, l_dir);
      lua_setglobal(L, "dir");
    
      return 0;
    }

整个示例有一个有趣的微妙之处。起初,似乎 dir_gc 应该检查其参数是否为目录。否则,恶意用户可以使用另一种用户数据(例如文件)调用它,从而产生灾难性后果。但是,Lua 程序无法访问此函数:它仅存储在目录的元表中,而 Lua 程序永远不会访问这些目录。