扩展提案 |
|
提案是 os 和 io 命名空间中额外非 ANSI 函数的 API 和实现。
最近(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 需要覆盖使用 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)
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) 或任何巨大的值似乎并不那么有用,但谁知道呢(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
(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
是的,这是一个已知的错误,我有一个修复程序,我只需要发布它,抱歉。-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); }
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