模块执行提案 |
|
这是一个关于新命令行开关 (-p
) 的提案,该开关通过 package.loaded
中的搜索器加载具有给定包名的函数,然后将该函数作为脚本执行,并将命令行参数作为参数传递给该函数。
lua -p modname args
示例
-- my/app.lua print("hello", io.read"*a", ...) local i=0; while arg[i] do print(i, arg[i]); i=i-1 end $ echo -n 123 | lua -p my.app 3 4 5 hello 123 3 4 5 0 my.app -1 -p -2 lua
这通常用于在 Lua 路径(例如 LUA_PATH)而不是系统 PATH 中安装 Lua 脚本,并且您希望找到并执行该脚本。该脚本可能具有作为模块和命令行实用程序的双重用途。也许这是一个基于 Lua 的分析器、调试器、代码验证器或在其他 Lua 代码上运行的宏处理器。
lua -p yet.another.debugger mybuggyprogram.lua 3 4 5 lua -p yet.another.preprocessor myprogram.m4 4 5 5
这里有先例——例如,Python 中的“-m”开关 [1]
-m mod : run library module as a script (terminates option list)
目前解决这个问题的替代方法并不令人满意。
首先,您可以将脚本安装到系统 PATH
中,但这依赖于系统,并且它将一个单独的文件放在 Lua 模块存储库之外的不同位置。
一种或多或少实现我们想要的功能的解决方案是
echo -n 123 | lua -e 'require "my.app" (3 4 5)'
其中 my.app 被修改为返回一个对提供的参数进行操作的函数。问题是命令行参数在字符串中被转义。如果应用程序要作为常规命令行实用程序使用,我们有很多理由想要避免这种情况。例如,我们可能希望在 bash 中使用别名定义 my.app 程序。建议的解决方案允许这样做
alias myapp="lua -p my.app" echo -n 123 | myapp 3 4 5
我们可能会尝试这样做
echo -n 123 | lua -lmy.app 3 4 5
由于 Lua 将“3”解释为文件名,因此会失败。我们可以使用以下技巧来解决这个问题
echo -n 123 | lua -lmy.app -e'os.exit(0)' 2 3 4
但是,参数不会通过 ...
或 arg
传递给 my.app 模块。它不会通过 ...
传递,因为“-l
”开关使用 require
,它对 ...
的定义不同,即包名。此外,当 Lua 解释器处理“-l”选项时,arg
表还没有构建。也许它应该构建,但无论如何,我们可能希望对 arg
全局变量有更优雅的解决方案。
另一种可能性可能是
echo -n 123 | lua -e 'require "my.app" (...)' 3 4 5
同样,my.app 被修改为返回一个评估传递给它的参数的函数。但这有两个问题。同样,Lua 将“3”解释为文件名。即使我们消除了这个问题,Lua 解释器也没有在 -e
语句中定义 ...
,我认为它应该 [4],这是一个单独的提案,在本文档的末尾给出。
允许将...
传递给-e
语句将是Lua的一个改进。它仍然是特立独行的,从包搜索机制加载脚本似乎足够基本,应该有一个带有方便语法的专用选项。当然,极简主义者会说这并非严格必要,但从文件系统或标准输入 (-) 加载模块的专用选项也是如此。
lua -e 'dofile("my/app.lua")' lua -e 'assert(loadfile())()' < my/app.lua
一个建议是扩展require
,以便它可以通过-l
加载时传递命令行参数。然而,require(x)
的本质是,对于相同的x
值,你总是得到相同的结果(幂等)。如果它没有记忆,它的语义将完全不同。[3]
一些其他语法已被提议用于“-p”选项
lua -Lmy.app args -- showing relationship to "-l" option lua +my.app args -- related to "-" for standard input source lua @my.app args -- proposed in [8] lua -m my.app args -- Python style lua -a my.app args -- load "application" lua -p my.app args -- load from *p*ackage *p*ath.
“-L
”被建议是因为它与“-l
”相似,但这种相似性可能具有误导性。“-l
”会经过require
,但“-L
”不会。“-L
”也将是第一个大写开关。
“+
”语法在其他方面非常好,但它可能不符合 POSIX 标准 [2],并且在文件名实际上以“+
”开头的奇特情况下,存在歧义。
lua ./+my.app args -- workaround using current directory "." lua -- +my.app args -- maybe treat as file name if followed by "--"?
“-m
”(如 Python 中)可能会与加载模块(-l
)混淆,这在 Perl 中是-m
,因此-m
似乎不可信。我们不是加载标准模块,而只是使用包路径(-p
)搜索机制来加载可能或可能不是完整模块的函数。
早期的 Lua 提案见 [7][8]。该提案提出了一个新的LUA_RPATH
环境变量,该变量将独立于LUA_PATH/LUA_CPATH
进行搜索。
LUA_RPATH=?.lua lua @my.app <parameters>
关于需要一个独立于LUA_PATH
的新环境变量,有一些疑问。其次,LUA_RPATH
只定位 Lua 源文件(如LUA_PATH
),但不会查询其他搜索器函数(如通过package.loaders
进行查询)——参见下面的“相关提案:新的 package.find 函数”。
以下补丁实现了上述-p
开关的提案。可以在 lua.c 中完全实现此补丁,但我们看到“-p
”开关和require
函数(来自 loadlib.c)共享许多共同的功能,因此将这些共同的功能分解到一个新的函数loadmodule
中似乎很有用,并且将该函数公开给 Lua 也可能很有用(如这里所做)。loadmodule
有点类似于其他load*
函数,并且如果用 Lua 编写,它具有这种行为。
local function loadmodule(name) local s = "" for i,loader in ipairs(package.loaders) do local f = loader(name) if type(f) == 'function' then return f end if f then s = s .. f end end return nil, s end
以下是针对 Lua 5.1.2 的补丁
一个相关的提案是让 '-e
' 开关终止命令行选项,并通过 ...
和 arg
接受命令行选项。
$ lua -e 'print(...)' 3 4 5 3 4 5
例如,Perl 和 Python 实现了这种行为。
$ perl -e 'print @ARGV' 3 4 5 345 python -c 'import sys; print(sys.argv)' 3 4 5 ['-c', '3', '4', '5']
比较 Lua 命令行格式和 Python 命令行格式。
usage: lua [options] [script [args]] usage: python [option] ... [-c cmd | -m mod | file | -] [arg] ...
Python 格式清楚地表明有四种不同类型的输入源,它们都以相同的方式处理,并且都可以接受命令行参数。Lua 格式可以改写为
lua [options] [(-e stat | -p mod | file | -) [arg]]
Lua 中唯一需要进一步更改的是让 -e
终止命令行选项并接受参数,如上所述。
请注意,以上内容可能意味着只能有一个 -e
开关。将 Lua 在此处的行为与 Perl 和 Python 进行比较。
$ lua -e 'print' -e '"1"' # invalid, each -e is treated # as a separate function $ perl -e 'print' -e '"1"' # valid, all -e's are concatenated $ python -c 'print' -c '"1"' # valid, but second -c is interpreted # as command-line argument
以上内容建议一个新的函数 loadmodule
,该函数根据包名加载函数。另一个可能很有用的函数是将包名映射到文件系统路径 [3]。
-- func = package.find(path, name) dofile(package.find(package.path, "foo"))
这可以通过纯 Lua 实现,请参阅 LuaModulesLoader。
但这只适用于映射到文件系统路径的模块(例如,通过 LUA_PATH
或 LUA_CPATH
)。对于 -p
开关提案,一个替代方案是基于 package.find
而不是 loadmodule
(这本质上是 Python 所做的),但这限制了可以使用此机制加载的函数类型,可能仅限于纯 Lua 文件,这也是 Python 的 PEP 338 [1][9] 建议取消此机制的原因。
模块如何确定它是否被 require
加载,而不是简单地执行(例如,通过 loadmodule
或 dofile
)?
理想情况下,人们希望将信息作为参数传递(在 ...
中),但这与当前做法不兼容,在当前做法中,...
如果作为脚本执行,则包含命令行参数,如果被 require
,则包含模块名。如果一个模块以两种方式使用,如果第一个命令行参数恰好是一个模块名,就会出现歧义。尝试
lua my/app.lua math
这里 ... == "math"
,所以 package.loaded[...]
被设置。package.loaded["my.app"]
可能没有被设置,但你需要在你的文件中硬编码包名 "my.app"。
另一种方法可能是使用全局变量(例如 arg
或 package.loaded
)或像 Python 一样。我们甚至可以像 local is_required = debug.getinfo(2, "f").func == require
一样查找堆栈,但这不仅有使用调试 [4] 的缺点,而且如果用具有等效功能的另一个函数替换 require
,就会失效 [3]。
在实践中(至少在 5.1 中),一个相当简单的解决方案是测试 `package.loaded` 中加载的哨兵 [5][3]。但这依赖于未定义的行为,并不是最干净的。理想情况下,我们希望用语言特性来代替它。
对 Lua 的一个提议更改如下。一个是类似于 `package.loaded` 表的 `package.loading` 表。它将包含 `require` *当前*正在加载的所有模块的包名。Lua 函数可以测试 `package.loading[...]` 来确定它是否正在被 `require`。一个相关的方法是函数 `package.status(name) --> loaded, loading, false`。这些方法的一个潜在问题是,如果你执行一个模块
lua my/app.lua a b c
其中 `a` 恰好是 *当前*正在加载的模块的名称(例如,可能在另一个协程中)。这种情况很少发生,但在某些情况下可能会发生。
解决所有这些问题的一个相当简单的解决方案是,只需为需要代码和运行代码使用单独的文件 [4]。但是,将它们放在同一个文件中可能很好(例如在单元测试中)——一方面,它是一种文档 [3]。
本文在一定程度上遵循 Python PEP 的格式 [6]。
作者:DavidManura,基于与 RiciLake、doub 和其他人的讨论。
创建日期:2007-09
Lua 5.1。
(无)