27

In python, one would usually define a main function, in order to allow the script to be used as module (if needed):

def main():
    print("Hello world")
    return 0

if __name__ == "__main__":
    sys.exit(main())

In Lua, the idiom if __name__ == "__main__" isn't possible as such (that means, I don't think it is).

That's what I'm usually doing in order to have a similar behaviour in Lua:

os.exit((function(args)
    print("Hello world")
    return 0
end)(arg))

... But this approach seems rather "heavy on parentheses" :-)

Is there a more common approach (besides defining a global main function, which seems redundant)?

9 Answers9

18

There's no "proper" way to do this, since Lua doesn't really distinguish code by where it came from, they are all just functions. That said, this at least seems to work in Lua 5.1:

matthew@silver:~$ cat hybrid.lua 
if pcall(getfenv, 4) then
    print("Library")
else
    print("Main file")
end
matthew@silver:~$ lua hybrid.lua 
Main file
matthew@silver:~$ lua -lhybrid
Library
Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
> ^C
matthew@silver:~$ lua
Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
> require "hybrid"
Library
> ^C
matthew@silver:~$

It works by checking whether the stack depth is greater than 3 (the normal depth for a file in the stock Lua interpreter). This test may break between Lua versions though, and even in any embedded/custom Lua builds.

I'll also include this (slightly more portable) alternative, although it's taking an even greater leap in heuristics, and has a failure case (see below):

matthew@silver:~$ cat hybrid2.lua 
function is_main(_arg, ...)
    local n_arg = _arg and #_arg or 0;
    if n_arg == select("#", ...) then
        for i=1,n_arg do
            if _arg[i] ~= select(i, ...) then
                print(_arg[i], "does not match", (select(i, ...)))
                return false;
            end
        end
        return true;
    end
    return false;
end

if is_main(arg, ...) then
    print("Main file");
else
    print("Library");
end
matthew@silver:~$ lua hybrid2.lua 
Main file
matthew@silver:~$ lua -lhybrid2
Library
Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
> ^C
matthew@silver:~$ lua 
Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
> require "hybrid2"
Library
>

This one works by comparing the contents of _G.arg with the contents of '...'. In the main chunk they will always be the same. In a module _G.arg will still contain the command-line arguments, but '...' will contain the module name passed to require(). I suspect this is closer to the better solution for you, given that you know your module name. The bug in this code lies when the user executes the main script with 1 argument, and this is the exact name of your module:

matthew@silver:~$ lua -i hybrid2.lua hybrid2
Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
Main file
> require "hybrid2"
Main file
> 

Given the above I hope at least you know where you stand, even if it isn't exactly what you had in mind :)

Update: For a version of hybrid.lua that works in Lua 5.1 and 5.2, you can replace getfenv with debug.getlocal:

if pcall(debug.getlocal, 4, 1) then
    print("Library")
else
    print("Main file")
end
MattJ
  • 7,924
  • 1
  • 28
  • 33
  • I wish I could upvote more than once :-). I guess i'll stick with the `getfenv` method for now, as it seems smaller. i'll definitely keep the other solution in mind. –  Dec 24 '10 at 05:42
  • @coldfix I've added some modified code that seems to work with 5.2. The rest of my answer still stands though, use at your own risk :) – MattJ Sep 11 '13 at 20:16
  • This looks good and much cleaner/less error prone than the second method. Is the corresponding scope guaranteed to have at least one entry? I posted a very similar solution using `debug.getinfo` instead of `debug.getlocal`, which makes the use of `pcall` unnecessary. Regards, Thomas – coldfix Sep 11 '13 at 20:27
  • Yes, arguably debug.getinfo() was the correct approach from the start. However I guess I was originally trying to avoid depending on the debug library if I didn't have to... – MattJ Sep 12 '13 at 21:56
  • Thanks, the updated answer works in Lua 5.1, 5.2 and 5.3. – selurvedu Sep 20 '16 at 07:22
7

you could try to check if the module has been required.

from documentation:

package.loaded A table used by require to control which modules are already loaded. When you require a module modname and package.loaded[modname] is not false, require simply returns the value stored there.

With this you could write:

if not package.loaded['modulename'] then
    main()
end
Łukasz Gruner
  • 2,929
  • 3
  • 26
  • 28
  • downside is, that you'd have to know the name of the module... `arg[0]` wouldn't work either, as lua modules are matched after `?.lua;?.BIN` (BIN being the file extension for a native library, .so or .dll for example) –  Dec 23 '10 at 17:44
  • what are you trying to accomplish? I assumed that you want to put this code into your own module, and know how it will be named. – Łukasz Gruner Dec 23 '10 at 18:06
  • btw, arg[0] returns the name of lua file that is being run, or that has required your module. try: test1.lua > require "test" test.lua > print(arg[0]) – Łukasz Gruner Dec 23 '10 at 18:07
  • "btw, arg[0] returns the name of lua file that is being run" - i've already mentioned that in my previous comment... –  Dec 23 '10 at 18:25
  • 1
    This does not work in lua5.2. The `package.loaded` entry will be inserted only when the module returns. – coldfix Sep 11 '13 at 17:05
7

When Lua requires a module, it passes it the name it's been required with as varargs (...).

So, if your script doesn't intend to take any arguments (from the command line or otherwise), you can use something like

if ... then
  return this_mod --module case
else
  main() --main case
end

Note, however, that this isn't foolproof in the (entirely) possible case that you take arguments. However, at this point, you can combine this with Lukasz's answer to get:

if not package.loaded[...] then
  --main case
else --module case
end

Still not perfect (for instance, if the script is called with a first argument of string or the name of some other already-loaded module), but likely good enough. In other situations, I defer to MattJ's answer.

Stuart P. Bentley
  • 10,195
  • 10
  • 55
  • 84
  • The second suggestion does not work in lua5.2. The package.loaded entry will be inserted only when the module returns – coldfix Sep 11 '13 at 17:23
4

I am going to suggest yet another variation, that seems to work on lua5.1 as well as lua5.2:

function is_main(offset)
    return debug.getinfo(4 + (offset or 0)) == nil
end

if is_main() then
    print("Main chunk!")
else
    print("Library chunk!")
end

If you do not feel like defining an extra function is_main for this purpose, you can just do:

if debug.getinfo(3) == nil then
    print("Main chunk!")
else
    print("Library chunk!")
end
coldfix
  • 6,604
  • 3
  • 40
  • 50
4

What's wrong with this:

$ cat aa.lua
#!/usr/bin/lua

if (arg ~= nil and arg[-1] ~= nil) then
    print "main"
else
    print "library"
end
$ ./aa.lua
main
$ ./aa.lua arg1 arg2
main
$ cat bb.lua
#!/usr/bin/lua

print("in bb")
$ lua -laa bb.lua
library
in bb
$ lua
Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
> require "aa"
library
> 
  • 3
    The problem is `arg` could be defined as a global variable in the calling code. Otherwise this is simple and great! – coldfix Sep 11 '13 at 17:03
2
if arg ~= nil and arg[0] == string.sub(debug.getinfo(1,'S').source,2) then
  print "Main file"
else
  print "Library"
end

An explanation:

  1. Lua calls a script from the interpreter with the arg table, with arg[0] being the name of the script.
  2. The debug.getinfo function returns a table of information describing the function at the level of the stack given by its numbered argument (1 refers to the function calling getinfo; 0 refers to getinfo itself). Its second parameter is optional: it specifies which subset of the getinfo fields to retrieve. The 'S' defines Source names.
  3. The source field of the table returned by getinfo for files contains the name of the file the function was defined in, prefixed with an @. By passing the value of that field to string.sub(...,2), we strip that @. (The short_src field contains the name without the @, but that's because it's defined as a "print-friendly" name, and is less safe than stripping source.)

Note that this code uses a debug function, so in 5.2 you'll have to require debug to be able to use it.

coldfix
  • 6,604
  • 3
  • 40
  • 50
Stuart P. Bentley
  • 10,195
  • 10
  • 55
  • 84
  • One should mention that this has a similar downfall as MattJ's answer: If `arg` is defined as a global variable and contains exactly the module name, you will think that the module is called as a main file. – coldfix Sep 11 '13 at 17:26
1

Maybe you can just deal with the debug library, with the debug.getinfo() function

if debug.getinfo(1).what == "main" then
    -- Main execution
end

See the reference manual for more information.

Faylixe
  • 478
  • 6
  • 12
  • 1
    Great idea. Unfortunately it does not work, since module code is considered 'main' chunk too. – coldfix Sep 11 '13 at 17:04
0

I'm using lua 5.3 and had issues with most of the suggestions here. This is what worked for my use case:

local my_module = {}
...
if os.getenv('CLI') then
  main()
else
  return my_module
end

When running from the command line I simple defined the environment variable such as:

CLI=1 lua my_script.lua

Works for me™

  • This doesn't work if more than one module in the dependency tree does this: at best, you'd have to have a "FOOBAR_CLI=1" (where "foobar" is the name of the module) for each module in the project - and forget about having more than one instance of that module (eg. to resolve a dependency version conflict)! – Stuart P. Bentley Apr 08 '22 at 02:25
0

after few test, only these can identify which file is run from command line, which file is load by [require] :


    print( debug.getinfo(1).short_src ) -- 
    print( debug.getinfo(1).source )    --
    print( debug.getinfo(2).short_src ) -- call this in a function
    print( debug.getinfo(2).source )    -- call this in a function

Flash Ang
  • 192
  • 2
  • 6