CLI#

Lua exposes command-line arguments through the global arg table. Index 0 is the script name; 1 is the first argument, 2 the second, and so on. arg[-1] (and lower) holds the interpreter command, which is rarely useful.

The stdlib has no argument parser. For anything beyond two or three positional arguments reach for argparse (a LuaRocks package modelled on Python’s argparse) or hand-rolls a small parser in twenty lines.

For the I/O surface scripts read and write through, see I/O. For packaging a script for distribution, see Runtime.

The arg table#

-- script.lua
print("script:", arg[0])
for i, v in ipairs(arg) do
  print(i, v)
end
$ lua script.lua -v --port 8080 host
script: script.lua
1   -v
2   --port
3   8080
4   host

#arg returns the number of positional arguments (everything from index 1 upward). The script’s own name lives at index 0 and is excluded from the count.

Hand-rolled parser#

For two or three flags, parsing inline is shorter than pulling in a library.

local opts = {verbose = false, port = 80}
local positional = {}

local i = 1
while i <= #arg do
  local a = arg[i]
  if     a == "-v" or a == "--verbose" then opts.verbose = true
  elseif a == "--port"                 then opts.port = tonumber(arg[i+1]); i = i + 1
  elseif a:sub(1,1) == "-"             then error("unknown flag: " .. a)
  else                                       positional[#positional + 1] = a
  end
  i = i + 1
end

for k, v in pairs(opts) do print(k, v) end
for _, p in ipairs(positional) do print("pos:", p) end

argparse (rock)#

For anything richer (subcommands, type coercion, --help generation) reach for argparse.

$ luarocks install argparse
local argparse = require("argparse")

local parser = argparse("scan", "Quick port scanner.")
parser:argument("host", "Target host.")
parser:option("-p --port", "Port to probe."):convert(tonumber):default(80)
parser:flag("-v --verbose", "Verbose output.")

local args = parser:parse()
print(args.host, args.port, args.verbose)
$ lua scan.lua --help
Usage: scan [-p <port>] [-v] [-h] <host>
...

Exit codes#

os.exit(n) exits with code n. The convention is 0 on success and a small positive integer on failure. os.exit does not run __gc finalisers; pass true as the second argument to force a clean shutdown.

if not ok then
  io.stderr:write("error: ", tostring(err), "\n")
  os.exit(1)
end

os.exit(0, true)            -- explicit success + finalise

Streaming#

Filter-style scripts read stdin, transform, and write stdout. This is the Unix-pipeline shape.

for line in io.stdin:lines() do
  io.stdout:write(line:upper(), "\n")
end
$ echo -e "one\ntwo" | lua upper.lua
ONE
TWO

The operator points errors and diagnostics at io.stderr so they do not pollute the data on stdout.

Shebang#

A #! line on the first line makes the script directly executable. env finds the interpreter on PATH.

#!/usr/bin/env lua

print("hello, " .. (arg[1] or "world"))
$ chmod +x greet.lua
$ ./greet.lua operator
hello, operator

References#