扩展提案 |
|
Proposal 是一个 API 和实现,用于在 os 和 io 命名空间中提供额外的非 ANSI 函数。
最近(2006 年 1 月)在邮件列表中进行的一次讨论促使我尝试设计一个扩展 API,该 API 通过向 os 和 io 命名空间添加函数来扩展 Lua API。
这不是一个修改 Lua 核心的提案,而是一个扩展 Lua 核心的 API 设计提案。该 API 旨在为当今主流操作系统(Windows、MacOSX 和 POSIX 平台)上的独立 Lua 程序提供一个更完整的编程环境。
在 LuaForge 上托管了 [POSIX 和 Windows 的实现]。这些实现具有很高的可用性,但在 API 仍处于标准化过程中时,仅应将其用于测试目的。
请注意,所有这些函数在失败时都返回标准的 (nil,"error message"),并且除非另有说明,否则它们在成功时返回 (true)。
require "ex"
os.getenv(name)
os.setenv(name, value)
os.setenv(name, nil)
os.environ()
os.chdir(pathname)
os.mkdir(pathname)
os.remove(pathname)
pathname = os.currentdir()
for entry in os.dir(pathname) do ; end
entry = os.dirent(pathname) entry = os.dirent(file)
os.dir() 返回的迭代器函数和 os.dirent() 函数都返回一个 'entry' 表。此表至少包含以下字段:
实现可能会添加其他字段甚至方法。
file:lock(mode, offset, length) io.lock(file, mode, offset, length)
file:unlock(offset, length) io.unlock(file, offset, length)
请注意,file:lock() 和 file:unlock() 都会扩展 Lua 文件对象的元表。
rd, wr = io.pipe()
os.sleep(seconds) os.sleep(interval, unit)
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]}
exitcode = proc:wait()
所有函数也可在 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 需要覆盖标准的 os.getenv 实现,该实现使用 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)
Mark,感谢你的实现。我也会加入到大家避免“污染” os 和 io 模块的呼声中,至少在当前实现中是这样,这样人们就可以在生产代码中使用这些模块,而不用担心将来如果 os 和 io 命名空间扩展接口标准化(为什么“仅用于测试目的”?)时发生冲突。如前所述,你的实现(不是你提出的接口)可能有一个 ex.install() 或类似函数,可以根据接口提案更新 os 和 io。这样用户现在就可以选择。--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) 或任何巨大的值似乎都没有太大用处,但谁知道呢(SleepEx? 中的 INFINITE 似乎很有用)。[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.
这是一个 bug;我认为现在已经修复了。-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
(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
其他对 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
是的,这是一个已知的 bug,我已经修复了,只是还没有发布,抱歉。-Mark
很高兴知道……但谁知道呢?如果你自己知道这些 bug,也许把这些知识与世界分享是个好主意?例如在这个页面上?好吧……<摇头>--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);
}
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.
这是一个已知 bug,现在已修复但尚未发布。很快!我保证。- MarkEdgar
由于不知道这一点,我重新实现了大部分函数。这有点重复代码,所以我将从现在开始只使用你的实现。不过我对 API 进行了一些调整,我认为这可能有用。我把这些笔记发布在我的页面上,因为它们很长--MauroIazzi
我认为 os.isatty 会很有用,这样可以避免在 "cat myfile | lua myapp.lua -" 中显式使用 "-"。Lua 本身就调用 isatty,尽管它在 luaconf.h 中--DavidManura
也许 glob 函数在这里会很有用。另请参阅 FileGlob。--DavidManura