# Lua Cheat Sheet: Lua 5.4 Syntax, Tables and Patterns

Source: https://srvscripts.com/guides/lua-cheat-sheet/
Updated: 2026-10-09
Publisher: srvScripts (https://srvscripts.com/)

**Short answer:** Lua 5.4 is a small language built around one data structure, the table, which serves as array, dictionary and object. Use `local` for every variable, `..` to join strings, `#t` for length, `ipairs` and `pairs` to loop, `pcall` to catch errors, and metatables with `__index` for classes. Arrays start at 1, only `nil` and `false` are false, and 5.4 adds integers, `//`, `<const>` and `<close>` variables.

The examples in this Lua cheat sheet were run with Lua 5.4.6 on 9 October 2026. Lua 5.5 has since been released; this sheet targets 5.4.

## Values, types and operators

| Syntax | What it does | Example |
| --- | --- | --- |
| local x = 1 | Local variable. Without local it is global | local port = 8080 |
| nil, boolean, number, string, table, function | Main types; type(x) returns the name | type({}) gives "table" |
| math.type(x) | "integer" or "float" (nil for non-numbers) | math.type(1.0) gives "float" |
| / and // | Float division / floor division | 7 / 2 is 3.5, 7 // 2 is 3, -7 // 2 is -4 |
| %, ^ | Modulo; power (always a float) | 2 ^ 10 gives 1024.0 |
| &, |, ~, , ~x | Bitwise on integers | 3 ~ 5 (XOR) gives 6 |
| ==, ~= | Equal / not equal. No type coercion | "10" == 10 is false |
| and, or, not | Return an operand, not a bool | local v = x or "default" |
| tonumber(s, base), tostring(v) | Conversions | tonumber("0x10") is 16; tonumber("abc") is nil |
| math.tointeger(3.0) | Float to integer if exact | Returns fail (nil) otherwise |

Only `nil` and `false` are false: `0` and `""` are true. Integers are 64-bit and wrap around: `math.maxinteger + 1 == math.mininteger` is true.

## Strings and patterns

| Function | What it does | Example |
| --- | --- | --- |
| .. | Concatenation | "Host " .. name .. ":" .. port |
| #s vs utf8.len(s) | Bytes vs UTF-8 characters | #"héllo" is 6, utf8.len gives 5 |
| s:upper(), s:sub(i, j) | Method syntax on strings; negative indexes count from the end | ("abcdef"):sub(-3) gives "def" |
| string.format() | printf-style | string.format("%-8s %5d", h, p) |
| s:find(text, 1, true) | Plain find (4th argument disables patterns) | ip:find(".", 1, true) |
| s:match(pat) | First match; returns captures | line:match("^(%w+)%s*=%s*(%w+)$") |
| s:gmatch(pat) | Iterate over matches | for w in s:gmatch("%a+") do … end |
| s:gsub(pat, repl) | Replace all; also returns the count | ("a,b"):gsub(",", ";") |
| string.rep(s, n, sep) | Repeat | string.rep("ab", 3, ",") |
| [[ … ]] | Long string, no escapes | Multi-line text |

Lua patterns are not regular expressions. There is no `|` alternation and no `{n}` counts, and the escape character is `%`, not backslash.

| Pattern item | Matches |
| --- | --- |
| %d, %a, %w, %s | Digit, letter, alphanumeric, whitespace (upper case negates: %S) |
| . and %. | Any character / a literal dot |
| *, +, -, ? | 0 or more (greedy), 1 or more, 0 or more (lazy), optional |
| [^/]+ | Character class and negation |
| ^, $, ( ) | Anchors and captures |
| (" x "):match("^%s*(.-)%s*$") | The usual trim idiom |

## Tables: arrays and dictionaries

| Syntax | What it does | Example |
| --- | --- | --- |
| {10, 20, 30} | Array part, indexes start at 1 | list[1] is 10 |
| {host = "db01", ["max-conn"] = 100} | Dictionary; brackets for keys that are not names | cfg.host, cfg["max-conn"] |
| #t | Length of a sequence (no nil holes) | list[#list + 1] = 40 appends |
| table.insert(t, v) / table.insert(t, 1, v) | Append / insert at a position | table.insert(list, 1, 5) |
| table.remove(t) | Remove and return the last (or given) element | table.remove(list, 1) |
| table.concat(t, sep) | Join strings and numbers | table.concat(list, ",") |
| table.sort(t, cmp) | Sort in place | table.sort(t, function(a, b) return a > b end) |
| table.unpack(t) / table.pack(...) | Spread / collect values (.n counts nils) | table.pack(1, nil, 3).n is 3 |
| t.key = nil | Delete a key | cfg.port = nil |
| next(t) == nil | Is the table empty? | Works for any table |

```
for i, v in ipairs(list) do print(i, v) end   -- 1..n in order, stops at the first nil
for k, v in pairs(cfg) do print(k, v) end     -- every key, order not defined
```

## Control flow

| Syntax | Example |
| --- | --- |
| if … then … elseif … else … end | if code >= 500 then … elseif code >= 400 then … end |
| Numeric for | for i = 10, 1, -3 do … end (start, stop, step) |
| Generic for | for line in io.lines(path) do … end |
| while / repeat … until | repeat i = i - 1 until i  2
```

## Errors: pcall, error and assert

| Call | What it does | Example |
| --- | --- | --- |
| error(msg, level) | Raise; level 0 omits the file:line prefix | error("bad port", 0) |
| pcall(f, ...) | Protected call: returns true, results or false, err | local ok, err = pcall(load_cfg) |
| xpcall(f, handler) | As pcall, with a message handler | xpcall(f, debug.traceback) |
| assert(v, msg) | Raise if v is false or nil; returns all its arguments | local fh = assert(io.open(path)) |
| return nil, "msg" | Library convention for soft failure | io.open returns nil and the reason |
| error({code = 42}) | Any value can be an error object | Check with type(err) == "table" |

## Metatables and objects

```
local Server = {}
Server.__index = Server

function Server.new(host, port)
  local self = setmetatable({}, Server)
  self.host, self.port = host, port or 22
  return self
end

function Server:dsn() return self.host .. ":" .. self.port end
Server.__tostring = function(s) return "Server(" .. s:dsn() .. ")" end

local srv = Server.new("db01", 3306)
print(srv:dsn(), tostring(srv))   --> db01:3306   Server(db01:3306)
```

| Metamethod | Called when |
| --- | --- |
| __index | A missing key is read (table or function); basis of classes and defaults |
| __newindex | A missing key is assigned; use for read-only tables |
| __tostring | tostring(obj) or print(obj) |
| __eq, __lt, __le | Comparisons |
| __add, __concat, __len | Arithmetic, .. and # |
| __call | The table is called like a function |
| __close | A  variable goes out of scope (5.4) |
| __gc | The object is garbage-collected |

For inheritance, give the subclass table a metatable whose `__index` is the parent: `local Web = setmetatable({}, {__index = Server})`, then `Web.__index = Web`.

## New in Lua 5.4 and coroutines

| Feature | Example | Note |
| --- | --- | --- |
| Constant variable | local limit  = 10 | Assigning again is a compile error |
| To-be-closed variable | local fh  = assert(io.open(p)) | fh is closed when the block ends, even on error |
| Integer subtype (5.3+) | math.type(3) is "integer" | 64-bit integers alongside floats |
| Warnings | warn("@on"), lua -W | Off by default |
| Coroutine generator | coroutine.wrap(function() coroutine.yield(1) end) | Call the result to resume |
| Coroutine control | coroutine.resume(co, x), coroutine.status(co) | Status is suspended, running or dead |

## Files, os and running commands

| Task | Code |
| --- | --- |
| Read a whole file | local fh = assert(io.open(p, "r")); local s = fh:read("a"); fh:close() |
| Loop over lines | for line in io.lines(p) do … end |
| Write or append | io.open(p, "w") / io.open(p, "a"), then fh:write(s) |
| Command output | local p = io.popen("uptime"); local out = p:read("a"); p:close() |
| Run a command | local ok, how, code = os.execute("true") |
| Environment and time | os.getenv("HOME"), os.time(), os.date("!%Y-%m-%d %H:%M:%S") |
| Delete or rename | os.remove(p), os.rename(a, b) |
| Load a module | local json = require("cjson") (searches package.path and package.cpath) |

**For server admins:** Lua is embedded in tools you may already run, but not always as 5.4. Redis scripts (`EVAL`) use Lua 5.1, and OpenResty uses LuaJIT, which is 5.1-compatible, so `//`, bitwise operators, `goto` (in Redis) and `<close>` are not available there. A quick status-code count over an access log in plain Lua 5.4:

```
local counts = {}
for line in io.lines("access.log") do
  local status = line:match('" (%d%d%d) ')
  if status then counts[status] = (counts[status] or 0) + 1 end
end
for code, n in pairs(counts) do print(code, n) end
```

**Official documentation (checked 9 October 2026):** [Lua 5.4 Reference Manual](https://www.lua.org/manual/5.4/manual.html) · [Lua 5.4 manual contents and index](https://www.lua.org/manual/5.4/) · [Lua 5.4 readme: changes since 5.3](https://www.lua.org/manual/5.4/readme.html) · [Lua versions](https://www.lua.org/versions.html)

## Lua Cheat Sheet at a glance

In short: A compact Lua 5.4 reference with tables for operators, string patterns, tables, functions, error handling, metatables and file access.

Lua Cheat Sheet: what this guide covers.

**Related guides:** [Luau Cheat Sheet: Type Annotations, Syntax and Roblox Basics](https://srvscripts.com/guides/luau-cheat-sheet/) · [Python Cheat Sheet: Syntax, Data Types and File Handling](https://srvscripts.com/guides/python-cheat-sheet/) · [C Cheat Sheet: C17 and C23 Syntax, Memory and gcc Flags](https://srvscripts.com/guides/c-cheat-sheet/).
