模組執行提案

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) 中安裝了 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

但是,引數不會傳遞至 my.app 模組,無論是透過 ...arg 傳遞。它不會透過 ... 傳遞,因為「-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)相混淆,而 -m 在 Perl 中是 -m,所以 -m 似乎不令人信服。我們並未載入標準模組,而只是使用套件路徑(-p)搜尋機制載入一個可能或可能不是完整展開的模組之函數。

先前在 [7][8] 中已針對這項點子提出 Lua 的建議。建議使用一個新的 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 剛好是當前正在載入的一個模組的名稱(例如,可能在另一個 coroutine 中)。這種情況較少見,但可能在某些情況下發生。

解決這一切的一個相當簡單的方法,就是對已載入的程式碼和正在執行中的程式碼使用不同的檔案 [4]。不過,將它們放在同一個檔案中可能會比較好(例如,在單元測試中)——從某方面來說,它是一種說明文件 [3]。

作者註解

這篇文章稍稍遵循 Python PEP [6] 的格式。

文件元資料

作者:DavidManura,根據與 RiciLake,doub,等人的討論所撰寫。

建立於:2007 年 9 月

Lua 5.1。

註解

(無)

參考


最近的修改 · 偏好設定
編輯 · 歷程
最後編輯於 2009 年 5 月 2 日,上午 2 點 15 分(格林尼治標準時間)(diff)