模块执行提案

lua-users home
wiki

摘要

这是一个关于新命令行开关 (-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 的补丁

文件:wiki_insecure/power_patches/5.1/module-exec.patch

相关提案:允许 -e 接受命令参数

一个相关的提案是让 '-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

相关提案:新的 package.find 函数

以上内容建议一个新的函数 loadmodule,该函数根据包名加载函数。另一个可能很有用的函数是将包名映射到文件系统路径 [3]。

-- func = package.find(path, name)
dofile(package.find(package.path, "foo"))

这可以通过纯 Lua 实现,请参阅 LuaModulesLoader

但这只适用于映射到文件系统路径的模块(例如,通过 LUA_PATHLUA_CPATH)。对于 -p 开关提案,一个替代方案是基于 package.find 而不是 loadmodule(这本质上是 Python 所做的),但这限制了可以使用此机制加载的函数类型,可能仅限于纯 Lua 文件,这也是 Python 的 PEP 338 [1][9] 建议取消此机制的原因。

相关提案:检测函数是否通过 Require 加载

模块如何确定它是否被 require 加载,而不是简单地执行(例如,通过 loadmoduledofile)?

理想情况下,人们希望将信息作为参数传递(在 ... 中),但这与当前做法不兼容,在当前做法中,... 如果作为脚本执行,则包含命令行参数,如果被 require,则包含模块名。如果一个模块以两种方式使用,如果第一个命令行参数恰好是一个模块名,就会出现歧义。尝试

lua my/app.lua math

这里 ... == "math",所以 package.loaded[...] 被设置。package.loaded["my.app"] 可能没有被设置,但你需要在你的文件中硬编码包名 "my.app"。

另一种方法可能是使用全局变量(例如 argpackage.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。

评论

(无)

参考资料


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2009 年 5 月 2 日凌晨 2:15 GMT (差异)