Lesson 3 of 10
Lua basics I: syntax and control flow
Variables, types, operators, conditionals and loops: the core of the language, with the gotchas that trip up newcomers.
Lua is a small, fast scripting language designed to be embedded in other programs. Its whole grammar fits on a page, which is why you can learn what NSE needs in two lessons. Recent Nmap releases embed Lua 5.3 or 5.4 (nmap --version tells you which); everything here works in both.
Trying Lua
Nmap doesn’t give you a REPL, but for practice you can install a standalone interpreter (lua5.4 on most Linux distributions, brew install lua on macOS) and run:
lua # interactive
lua file.lua # run a file
Or paste snippets into any online Lua 5.4 playground. Everything in these two lessons is plain Lua, not NSE-specific.
Comments, variables and scope
-- a single-line comment
--[[ a multi-line
comment ]]
local name = "nmap" -- local: visible in this block only
port = 80 -- global: visible everywhere (avoid!)
Types
Lua has eight types. You’ll use six of them daily:
| Type | Example | Notes |
|---|---|---|
nil | nil | “no value”. An unset variable is nil. |
boolean | true, false | |
number | 42, 3.14 | Integers and floats (Lua 5.3+ distinguishes them). |
string | "text", 'text' | Immutable byte strings, so they’re safe for binary data. |
table | {1, 2, 3}, {a = 1} | The only data structure: arrays and dictionaries. |
function | function() end | Functions are values. |
print(type(nil), type(true), type(3), type("x"), type({}), type(print))
--> nil boolean number string table function
Truthiness: the rule that surprises everyone
In a condition, only nil and false are false. Everything else is true, including 0 and the empty string.
if 0 then print("zero is true") end -- prints
if "" then print("empty string is true") end -- prints
if nil then print("never") end
Numbers and operators
print(7 / 2, 7 // 2, 7 % 2, 2 ^ 10) --> 3.5 3 1 1024.0
print(10 == 10.0) --> true
print(3 ~= 4) --> true (not-equal is ~=, not !=)
print(not nil, true and 5, false or "default") --> true 5 default
Two operators worth memorising:
..concatenates strings:"port " .. 80gives"port 80". Numbers are converted automatically, butnilis not:"x" .. nilraises an error. That’s the number-one NSE runtime error.#gives the length of a string or an array-style table:#"nmap"is4.
The and/or default idiom
a or b returns a if it’s truthy, otherwise b. You’ll use it for defaults everywhere:
local timeout = tonumber(user_value) or 5000
local path = args.path or "/"
The ternary version is cond and x or y. It breaks if x can be false or nil, so use an if when in doubt.
Strings
local s = "Hello, NSE"
print(#s) --> 10
print(s:upper(), s:lower()) --> HELLO, NSE hello, nse
print(s:sub(1, 5)) --> Hello (1-based, inclusive)
print(("%s:%d"):format("10.0.0.5", 80)) --> 10.0.0.5:80
print(tostring(80) .. "/tcp") --> 80/tcp
print(tonumber("0x50"), tonumber("abc")) --> 80 nil
s:method(...) is sugar for string.method(s, ...). The colon passes s as the first argument. Forgetting the colon (s.upper()) is a classic beginner mistake. Long strings use [[ ... ]], which is what NSE uses for description.
Conditionals
local state = "open"
if state == "open" then
print("scan it")
elseif state == "filtered" then
print("maybe")
else
print("skip")
end
Note elseif (one word), then, and the closing end.
Loops
-- numeric for: start, stop (inclusive), optional step
for i = 1, 3 do print(i) end --> 1 2 3
for i = 10, 1, -3 do print(i) end --> 10 7 4 1
-- while and repeat
local n = 3
while n > 0 do n = n - 1 end
repeat n = n + 1 until n >= 3
-- iterating tables (next lesson covers tables properly)
local ports = {22, 80, 443}
for i, p in ipairs(ports) do print(i, p) end -- ordered, stops at first nil
local t = {a = 1, b = 2}
for k, v in pairs(t) do print(k, v) end -- any keys, unspecified order
break leaves a loop early; goto continue with a ::continue:: label is Lua’s continue.
ipairs versus pairs: use ipairs for arrays (predictable order), pairs for dictionaries. Never rely on pairs order.
Lab
- Port classifier. Given a number, print
"well-known"for 1 to 1023,"registered"for 1024 to 49151,"dynamic"for 49152 to 65535, and"invalid"otherwise. - Port range expander. Loop from 8000 to 8005 and print
"8000/tcp","8001/tcp"and so on using... - Default arguments. Set
local depth = tonumber(nil) or 3and print it. Then change it totonumber("7") or 3. - Truthiness check. Predict, then run:
print(0 and "a" or "b"),print(nil and "a" or "b").
Solution sketch for exercise 1
local function classify(p)
if type(p) ~= "number" or p < 1 or p > 65535 then return "invalid" end
if p <= 1023 then return "well-known" end
if p <= 49151 then return "registered" end
return "dynamic"
end
print(classify(22), classify(8080), classify(60000), classify(0))
Checkpoint
What does print("port " .. nil) do?
It raises “attempt to concatenate a nil value”. Convert with tostring() or check for nil first.
What does print(0 and "a" or "b") print?
a. Zero is truthy in Lua.