扩展提案

lua-users home
wiki

扩展提案是 os 和 io 命名空间中额外非 ANSI 函数的 API 和实现。

描述

最近(2006 年 1 月)邮件列表上的讨论促使我尝试设计一个扩展 API,该 API 通过向 os 和 io 命名空间添加函数来扩展 Lua API。

这不是修改 Lua 核心代码的提案,而是一个扩展 Lua 核心的 API 设计提案。该 API 旨在为当今流行的操作系统(Windows、MacOSX 和 POSIX 平台)上的独立 Lua 程序提供更完整的编程环境。

来源

LuaForge 上托管了 [POSIX 和 Windows 的实现]。这些是高度可用的实现,但应仅在 API 仍在标准化期间用于测试目的。

ex API

注意,所有这些函数在失败时返回标准 (nil,"error message"),并且除非另有说明,否则它们在成功时返回 (true)。

初始化

require "ex"
标记使用此 API 的 Lua 程序

环境

os.getenv(name)
获取环境值

os.setenv(name, value)
设置/修改环境值

os.setenv(name, nil)
删除环境值

os.environ()
返回环境的副本(一个简单的 Lua 表格)

文件系统(主要借鉴自 LuaFilesystem?

os.chdir(pathname)
更改工作目录

os.mkdir(pathname)
创建目录

os.remove(pathname)
删除文件或目录

pathname = os.currentdir()
获取工作目录路径

for entry in os.dir(pathname) do ; end
迭代目录中的条目。pathname 参数是可选的;如果缺失,则使用当前目录。
不会返回诸如 "." 和 ".." 之类的特殊目录条目。

entry = os.dirent(pathname)
entry = os.dirent(file)
通过路径名或打开的文件获取有关目录条目的信息

os.dir() 返回的迭代器函数和 os.dirent() 函数都返回一个 'entry' 表格。此表格至少包含以下字段

entry.name= 文件名(注意 os.dirent() 不需要设置此字段)
entry.type= "file" 或 "directory"(或实现定义的字符串)
entry.size= 文件大小(以字节为单位)

实现可能会添加其他字段甚至方法。

I/O(锁定和管道)

file:lock(mode, offset, length)
io.lock(file, mode, offset, length)
锁定或解锁文件或文件的一部分;'mode' 为 "r" 或 "w" 或 "u";'offset' 和 'length' 是可选的
模式“r”请求读锁,“w”请求写锁,“u”释放锁。请注意,锁可以是建议性的,也可以是强制性的。

file:unlock(offset, length)
io.unlock(file, offset, length)
等效于 file:lock("u", offset, length) 或 io.lock(file, "u", offset, length)

请注意,file:lock() 和 file:unlock() 都会扩展 Lua 文件对象的元表。

rd, wr = io.pipe()
创建管道;'rd' 和 'wr' 是 Lua 文件对象

进程控制

os.sleep(seconds)
os.sleep(interval, unit)
将程序执行挂起 interval/unit 秒;'unit' 默认值为 1,两个参数都可以是浮点数。具体的亚秒精度由实现定义。
os.sleep(3.8) -- sleep for 3.8 seconds
local microseconds = 1e6
os.sleep(3800000, microseconds) -- sleep for 3800000 �s
local ticks = 100
os.sleep(380, ticks) -- sleep for 380 ticks

proc = os.spawn(filename)
proc = os.spawn{filename, [args-opts]}
proc = os.spawn{command=filename, [args-opts]} 
创建子进程

'filename' 指定程序名称。如果(实现定义的)路径名不是绝对路径,则程序将通过实现定义的搜索方法找到(大多数系统上的 PATH 环境变量)。

如果指定,[args-opts] 是以下一个或多个键

[1]..[n]= 命令行参数

args= 命令行参数数组

env= 环境变量表

stdin= stdout= stderr= 分别用于标准输入、输出和错误的 io.file 对象

如果同时指定整数键和 'args' 键,则会发生错误。

如果提供第零个参数(options.args[0] 或 options[0]),实现可能会提供特殊行为。

返回的 'proc' 用户数据具有以下方法

exitcode = proc:wait()
等待子进程终止;'exitcode' 是子进程返回的代码

摘要

所有函数也可以在 ex 命名空间下使用

ex.getenv(name)
ex.setenv(name, value)
ex.environ()
ex.chdir(pathname)
ex.mkdir(pathname)
ex.currentdir()
ex.dir(pathname)
ex.dirent(pathname)
ex.lock(file, mode, offset, length)
ex.unlock(file, offset, length)
ex.pipe()
ex.sleep(interval, [unit])
ex.spawn(...)
ex.wait(proc)

请注意,ex.getenv 主要用于并行处理,但也因为在 Windows 下,使用 SetEnvironmentVariable?() API 需要覆盖使用 getenv() 的标准 os.getenv 实现,以使用 GetEnvironmentVariable?() 代替。

示例

require "ex"

-- run the echo command
proc = os.spawn"/bin/echo"
proc = os.spawn{"/bin/echo", "hello", "world"}
proc = os.spawn{command="/bin/echo", "hello", "world"}

-- run the id command
vars = { LANG="fr_FR" }
proc = os.spawn{"/bin/id", "-un", env=vars}
proc = os.spawn{command="/bin/id", "-un", env=vars)

-- Useless use of cat
local rd, wr = assert(io.pipe())
local proc = assert(os.spawn("/bin/cat", {stdin=rd}))
rd:close()
wr:write("Hello world\n")
wr:close()
proc:wait()

-- Run a program with a modified environment
local env = os.environ()
env.LUA_PATH = "/usr/share/lib/lua/?.lua"
env.LUA_CPATH = "/usr/share/lib/lua/?.so"
local proc = assert(os.spawn("lua", {args = {"-e", 'print"Hello world\n"'}, env=env }))
proc:wait()

-- popen2()
function popen2(...)
  local in_rd, in_wr = io.pipe()
  local out_rd, out_wr = io.pipe()
  local proc, err = os.spawn{stdin = in_rd, stdout = out_wr, ...}
  in_rd:close(); out_wr:close()
  if not proc then
    in_wr:close(); out_rd:close()
    return proc, err
  end
  return proc, out_rd, in_wr
end
-- usage:
local p, i, o = assert(popen2("wc", "-w"))
o:write("Hello world"); o:close()
print(i:read"*l"); i:close()
p:wait()

评论

Mark,我建议你将你的工作视为一个实用程序模块,而不是将函数插入标准库命名空间,为它选择一个名称,并将函数放在新的模块名称下。如果你的函数像现在这样流行起来,就会造成混乱,特别是对于那些可能阅读一些示例代码但不知道标准模块中的某些函数为什么不在上游文档中的 Lua 初学者来说。--JohnBelmonte

我同意 John 的观点。也许可以使用 ex.install() 或类似的方法将其安装到 os 库中。但我更希望它是一个单独的库。--Doug Rogers

我同意之前的评论。我更希望不要将函数注入到“os”中,而是拥有单独的库。'

env.getenv(name)
env.setenv(name, value)
env.environ()
--no unsetenv. It's meaningless.
--function env.unsetenv(name)
--    env.setenv(name, nil)
--end

fs.chdir(pathname)
fs.mkdir(pathname)
fs.currentdir()
fs.dir(pathname)
fs.dirent(pathname)
fs.lock(file, mode, offset, length)
fs.unlock(file, offset, length)

proc.pipe()
proc.sleep(seconds)
proc.spawn(...)
proc.wait(proc)
--ncopa

Mark,感谢你的实现。我将加入合唱团,避免“污染”os 和 io 模块,至少在当前实现中是这样,这样人们就可以在生产代码中使用这些模块,而不用担心将来如果和何时扩展 os 和 io 命名空间的接口被标准化(为什么“仅用于测试目的”?)。如前所述,你的实现(而不是你提出的接口)可能有一个 ex.install() 或类似的函数,它将根据接口提案更新 osio。这为用户提供了现在的选择。--DavidManura

更进一步,如果你要将这些内容提取到单独的表格中(例如 filesys),从 os 中复制与文件相关的条目可能会有用。这样可以使新代码更简洁,并在调用此扩展时使 os 表格“过时”。


最新版本的评论。

(1)在默认的 conf.in 中,将 "-llua51" 更改为 "-llua5.1",以与 LuaBinaries 保持一致?

这与 Lua 发行版一致,但我可以在 conf.in 中添加一个注释。-Mark

我一直想知道为什么 Lua 和 LuaBinaries 之间存在这种不一致。

(2)os.sleep(math.huge) 立即返回。它不应该永远不返回吗?

我需要调查一下为什么会发生这种情况... -Mark

os.sleep(1e100) 也立即返回。Win32 Sleep(INFINITE) 或任何巨大的值似乎并不那么有用,但谁知道呢(INFINTE 在 SleepEx? 中似乎有用)。 [1] Sleep(0) 表示“放弃时间片”。

(3)os.setenv 和 os.mkdir(1) - 实现自动将数字参数转换为字符串。这是预期的吗?

可能是 - 它们需要一个字符串。-Mark

我在 string.lower(123) == "123" 中也看到了这种行为(为真)。

(4)这是预期的吗?

> =os.setenv("a", {})
nil     203 (0xCB): The system could not find the environment option that was entered.
> =os.setenv("a", nil)
nil     203 (0xCB): The system could not find the environment option that was entered.

API 没有说明如果将非字符串作为第二个参数传递,或者如果尝试删除一个不存在的变量会发生什么。它应该说明吗?

不知道,但错误消息很奇怪。

(5)将 os.dir()(没有参数)更改为使用当前工作目录?

这是一个好主意;它将需要成为 API 的一部分。-Mark

(6)当我调用 os.dirent 时,entry.name 丢失了。

是的,这是预期的,并且 API 的说明不正确。除非有理由应该不同?-Mark

(7)这是预期的吗?

> f = io.open("123")
> = io.lock(f, "w")
nil     6 (0x6): The handle is invalid.
> =f
file (0080F060)

是的,ex 没有说明如果你尝试对只读文件进行写锁定会发生什么。它应该说明吗?-Mark

可能不会。在 Win32 下会怎么样?

Lua 5.1.1  Copyright (C) 1994-2006 Lua.org, PUC-Rio
> require "ex"
> f = assert(io.open("234", "w"))
> = f:lock("w")
nil     6 (0x6): The handle is invalid.

这是一个错误;我认为现在已经修复了。-Mark

(8)

$ make mingw
make -C w32api ex.dll
make[1]: Entering directory `.../ex/w32api'
cc -W -Wall -Wno-missing-braces -DWIN32_LEAN_AND_MEAN -DNOGDI -...  -mno-cygwin -c -o ex.o ex.c
cc -W -Wall -Wno-missing-braces -DWIN32_LEAN_AND_MEAN -DNOGDI -...  -mno-cygwin -c -o spawn.o spawn.c
spawn.c: In function `spawn_param_init':
spawn.c:35: warning: missing initializer
spawn.c:35: warning: (near initialization for `si.lpReserved')
cc -W -Wall -Wno-missing-braces -DWIN32_LEAN_AND_MEAN -DNOGDI -I...  -mno-cygwin -c -o pusherror.o pusherror.c
cc -W -Wall -Wno-missing-braces -DWIN32_LEAN_AND_MEAN -DNOGDI -I...  -mno-cygwin -c -o dirent.o dirent.c
cc -mno-cygwin -shared -o ex.dll ex.o spawn.o pusherror.o dirent.o -L... -llua5.1
make[1]: Leaving directory `.../ex/w32api'

参见 http://msdn2.microsoft.com/en-us/library/ms686331.aspx 可能需要通过 memset 初始化结构以避免警告。

警告是 GCC 的一个不幸特性 - C 保证其他结构成员默认初始化为零,这是这里的意图。-Mark

可以使用 "static const STARTUPINFO si = {sizeof si, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};" 避免警告。

(9)

$ make cygwin
make -C posix T=ex.dll ex.dll
make[1]: Entering directory `...ex/posix'
cc -W -Wall -std=c89 -D_XOPEN_SOURCE=600 -DMISSING_POSIX_SPAWN -DENVIRON_DECL="e
xtern char **environ;" -I...   -c -o ex.o ex.c
ex.c: In function `ex_setenv':
ex.c:53: warning: implicit declaration of function `setenv'
ex.c:53: warning: implicit declaration of function `unsetenv'
ex.c: In function `new_file':
ex.c:131: warning: implicit declaration of function `fdopen'
ex.c:131: warning: assignment makes pointer from integer without a cast
ex.c: In function `ex_dirent':
ex.c:151: warning: implicit declaration of function `fileno'
cc -W -Wall -std=c89 -D_XOPEN_SOURCE=600 -DMISSING_POSIX_SPAWN -DENVIRON_DECL="e
xtern char **environ;" -I...   -c -o spawn.o spawn.c
cc -W -Wall -std=c89 -D_XOPEN_SOURCE=600 -DMISSING_POSIX_SPAWN -DENVIRON_DECL="e
xtern char **environ;" -I...   -c -o posix_spawn.o po
six_spawn.c
posix_spawn.c:49: warning: unused parameter 'act'
cc -shared -o ex.dll ex.o spawn.o posix_spawn.o -L...
make[1]: Leaving directory `...ex/posix'

注意 cygwin stdlib.h

#ifndef __STRICT_ANSI__
long    _EXFUN(a64l,(const char *__input));
char *  _EXFUN(l64a,(long __input));
char *  _EXFUN(_l64a_r,(struct _reent *,long __input));
int	_EXFUN(on_exit,(_VOID (*__func)(int, _PTR),_PTR __arg));
_VOID	_EXFUN(_Exit,(int __status) _ATTRIBUTE ((noreturn)));
int	_EXFUN(putenv,(char *__string));
int	_EXFUN(_putenv_r,(struct _reent *, char *__string));
int	_EXFUN(setenv,(const char *__string, const char *__value, int __overwrite));
int	_EXFUN(_setenv_r,(struct _reent *, const char *__string, const char *__value, int __overwrite))
#ifndef __STRICT_ANSI__
#ifndef _REENT_ONLY
FILE *	_EXFUN(fdopen, (int, const char *));
#endif
int	_EXFUN(fileno, (FILE *));

ExtensionProposal 的目的是提供非 ANSI 函数,那么为什么要在 ANSI 下编译呢?

好的,-std=c89 是错误的——我会更改 conf.in。-Mark

--DavidManura

(10) 似乎 ex.dir 打开了一个文件但没有关闭它。因此这段代码

while true do for f in os.dir("./") do print(f.name) end end

在一些循环后会报错

stdin:2: attempt to call a nil value stack traceback

stdin:2: in main chunk
[C]: ?

其他对 os.dir 的调用会给出

print(os.dir("./")) -> nil Too many open files

lsof 显示该目录有多个 (1020) 副本处于打开状态。

我知道打开的文件是必须跨多个 ex API 调用存在的,但使用打开的文件调用 os.dir 的可能性可以提供这种临时的解决方法

while true do local d = io.open("./") for f in os.dir(d) do print(f.name) end d:close() end

顺便说一句,这个 ex 非常有用。很棒的工作——m.i。

在对 POSIX 手册进行快速搜索后,似乎并不容易或不可能。相反,在 posix/ex.c 中更改几行代码(在第 240 行,在 ex_dir 中)从

if (!d) return push_error(L);

if (!d) { diriter_close(L); return push_error(L); }

似乎解决了这个问题(并且没有创建其他问题,但这需要测试)。

抱歉没有使用 diff,我从未学过……

再次,很棒的工作——m.i。

是的,文件句柄在内存耗尽之前就用完了,所以你的解决方案是正确的。顺便说一句,这可能是一个更好的习惯用法

for f in assert(os.dir".") do print f.name; end

-Mark


以下函数(来自 w32api\spawn.c,当前版本)如果有多个参数,则会可靠地崩溃。我认为 lua_pop() 是错误的。基本上,相同的错误似乎(实际上是两次)潜伏在 spawn_param_env() 中。我没有查看 posix 实现……祝你好运。-- ThomasLauer

是的,这是一个已知的错误,我有一个修复程序,我只需要发布它,抱歉。-Mark

很高兴知道……但谁知道?如果*你*知道这些错误,也许与更广泛的世界分享这些知识会是一个好主意?例如,在这个页面上?好吧……<摇头>-- ThomasLauer

void spawn_param_args(struct spawn_params *p)
{
	 lua_State *L = p->L;
	 debug("spawn_param_args:"); debug_stack(L);
	 luaL_Buffer args;
	 luaL_buffinit(L, &args);
	 size_t i, n = lua_objlen(L, -1);
	 /* concatenate the arg array to a string */
	 for (i = 1; i <= n; i++) {
	 	 int quote;
	 	 lua_rawgeti(L, -1, i);     /* ... argtab arg */
	 	 /* XXX checkstring is confusing here */
	 	 quote = needs_quoting(luaL_checkstring(L, -1));
	 	 luaL_putchar(&args, ' ');
	 	 if (quote) luaL_putchar(&args, '"');
	 	 luaL_addvalue(&args);
	 	 if (quote) luaL_putchar(&args, '"');
--->	 lua_pop(L, 1);             /* ... argtab */
	 }
	 luaL_pushresult(&args);        /* ... argtab argstr */
	 lua_pushvalue(L, 1);           /* cmd opts ... argtab argstr cmd */
	 lua_insert(L, -2);             /* cmd opts ... argtab cmd argstr */
	 lua_concat(L, 2);              /* cmd opts ... argtab cmdline */
	 lua_replace(L, -2);            /* cmd opts ... cmdline */
	 p->cmdline = lua_tostring(L, -1);
}


当我尝试执行以下操作时,我遇到了 SEGFAULT
require 'ex'
ex.spawn{'ls'}

在 lua 5.1.1 解释器中(在 gentoo、amd64、gcc-4.1.2 上)。Gdb 回溯

#0  0x00002adc9351a885 in raise () from /lib/libc.so.6
#1  0x00002adc9351bb3e in abort () from /lib/libc.so.6
#2  0x00002adc93550a27 in ?? () from /lib/libc.so.6
#3  0x00002adc93555b1d in ?? () from /lib/libc.so.6
#4  0x00002adc935579b6 in ?? () from /lib/libc.so.6
#5  0x00002adc9355950d in malloc () from /lib/libc.so.6
#6  0x00002adc93177351 in ?? () from /usr/lib64/liblua.so.5
#7  0x00002adc9317add9 in ?? () from /usr/lib64/liblua.so.5
#8  0x00002adc93170237 in lua_getfield () from /usr/lib64/liblua.so.5
#9  0x00002adc938867e9 in ex_spawn (L=0x503010) at ex.c:412
#10 0x00002adc93173fc1 in ?? () from /usr/lib64/liblua.so.5
#11 0x00002adc9317d50e in ?? () from /usr/lib64/liblua.so.5
#12 0x00002adc9317440d in ?? () from /usr/lib64/liblua.so.5
#13 0x00002adc93173b77 in ?? () from /usr/lib64/liblua.so.5
#14 0x00002adc93173bf4 in ?? () from /usr/lib64/liblua.so.5
#15 0x00002adc9316fc75 in lua_pcall () from /usr/lib64/liblua.so.5
#16 0x0000000000401746 in ?? ()
#17 0x0000000000401b54 in ?? ()
#18 0x0000000000402137 in ?? ()
#19 0x00002adc93173fc1 in ?? () from /usr/lib64/liblua.so.5
#20 0x00002adc931743bd in ?? () from /usr/lib64/liblua.so.5
#21 0x00002adc93173b77 in ?? () from /usr/lib64/liblua.so.5
#22 0x00002adc93173bf4 in ?? () from /usr/lib64/liblua.so.5
#23 0x00002adc9316fc17 in lua_cpcall () from /usr/lib64/liblua.so.5
#24 0x000000000040167d in main ()

这是使用标准(Gentoo 发行版)lua 5.1.1。

我编译了 lua 5.1.2,得到了相同的结果(更多符号)。

使用 -O0 编译 lua 仍然出现相同的错误。只是信息更多。

#0  0x00002b8a1dc7a885 in raise () from /lib/libc.so.6
#1  0x00002b8a1dc7bb3e in abort () from /lib/libc.so.6
#2  0x00002b8a1dcb0a27 in ?? () from /lib/libc.so.6
#3  0x00002b8a1dcb5b1d in ?? () from /lib/libc.so.6
#4  0x00002b8a1dcb79b6 in ?? () from /lib/libc.so.6
#5  0x00002b8a1dcb950d in malloc () from /lib/libc.so.6
#6  0x0000000000419c8d in l_alloc (ud=0x0, ptr=0x0, osize=0, nsize=29) at lauxlib.c:636
#7  0x000000000040d519 in luaM_realloc_ (L=0x533010, block=0x0, osize=0, nsize=29) at lmem.c:79
#8  0x0000000000411e3b in newlstr (L=0x533010, str=0x2b8a1de8c235 "args", l=4, h=7976507) at lstring.c:56
#9  0x00000000004120ab in luaS_newlstr (L=0x533010, str=0x2b8a1de8c235 "args", l=4) at lstring.c:92
#10 0x00000000004060f4 in lua_getfield (L=0x533010, idx=2, k=0x2b8a1de8c235 "args") at lapi.c:546
#11 0x00002b8a1de8b4be in ex_spawn (L=0x533010) at ex.c:379
#12 0x0000000000409e92 in luaD_precall (L=0x533010, func=0x533410, nresults=0) at ldo.c:319
#13 0x00000000004175b9 in luaV_execute (L=0x533010, nexeccalls=1) at lvm.c:589
#14 0x000000000040a106 in luaD_call (L=0x533010, func=0x533400, nResults=-1) at ldo.c:377
#15 0x0000000000406a30 in f_call (L=0x533010, ud=0x7fff8d573bb0) at lapi.c:796
#16 0x0000000000409192 in luaD_rawrunprotected (L=0x533010, f=0x406a01 <f_call>, ud=0x7fff8d573bb0) at ldo.c:116
#17 0x000000000040a49d in luaD_pcall (L=0x533010, func=0x406a01 <f_call>, u=0x7fff8d573bb0, old_top=48, ef=32) at ldo.c:461
#18 0x0000000000406ad6 in lua_pcall (L=0x533010, nargs=0, nresults=-1, errfunc=1) at lapi.c:817
#19 0x0000000000403dfa in docall (L=0x533010, narg=0, clear=0) at lua.c:100
#20 0x00000000004043d0 in dotty (L=0x533010) at lua.c:219
#21 0x0000000000404b3f in pmain (L=0x533010) at lua.c:367
#22 0x0000000000409e92 in luaD_precall (L=0x533010, func=0x5333e0, nresults=0) at ldo.c:319
#23 0x000000000040a0f4 in luaD_call (L=0x533010, func=0x5333e0, nResults=0) at ldo.c:376
#24 0x0000000000406be0 in f_Ccall (L=0x533010, ud=0x7fff8d573f50) at lapi.c:842
#25 0x0000000000409192 in luaD_rawrunprotected (L=0x533010, f=0x406b11 <f_Ccall>, ud=0x7fff8d573f50) at ldo.c:116
#26 0x000000000040a49d in luaD_pcall (L=0x533010, func=0x406b11 <f_Ccall>, u=0x7fff8d573f50, old_top=16, ef=0) at ldo.c:461
#27 0x0000000000406c37 in lua_cpcall (L=0x533010, func=0x404973 <pmain>, ud=0x7fff8d573fa0) at lapi.c:852
#28 0x0000000000404bb4 in main (argc=1, argv=0x7fff8d5740b8) at lua.c:385

ex.spawn'ls' 语法也失败了,但在 spawn_param_execute 中失败。

我不明白为什么。

传递给 realloc 的参数应该是正确的(gdb 将它们视为 ptr=0x0,nsz=48),但从调试输出来看,它似乎调用了 malloc(这正常吗?)。我在正常的 Lua 使用中没有遇到这种类型的错误,所以我认为这不是它的错。

--m.i.

这是一个已知的错误,现在已修复,但尚未发布。很快!我保证。 - MarkEdgar

由于不知道这一点,我重新实现了大多数函数。这有点像重复代码,所以从现在开始我将坚持使用您的实现。不过,我对 API 做了一些调整,我认为这些调整可能会有用。我将笔记发布在我的页面上,因为它们很长。-- MauroIazzi

我认为 os.isatty 将很有用,可以避免在 "cat myfile | lua myapp.lua -" 中显式使用 "-"。Lua 本身调用 isatty,尽管它在 luaconf.h 中。--DavidManura

也许一个 glob 函数在这里会有用。另请参见 FileGlob。--DavidManura


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2009 年 10 月 31 日下午 8:01 GMT (差异)