# Lua to Perl You know Lua. You build a table without thinking, you reach for `setmetatable` to get objects and operators, and `for k, v in pairs(t)` is muscle memory. This guide maps each of those habits onto Perl, so you can be productive in `pperl` in an afternoon instead of relearning programming from scratch. Two shocks land in the first five minutes, so meet them now: - **Lua's one table becomes two Perl types.** Lua has a single aggregate, the table, that you use as both an array (`{1, 2, 3}`) and a map (`{k = "v"}`). Perl splits that into two distinct things: an ordered `@array` keyed by integers and a `%hash` keyed by strings. Deciding "is this a list or a lookup?" up front is the central new habit, and the [Arrays](#arrays-and-lists) and [Hashes](#hashes) sections are about nothing else. - **Variables wear sigils.** Lua names are bare: `x`, `t`, `f`. In Perl a scalar is `$x`, an array is `@x`, a hash is `%x`. The sigil is part of how you read the variable, not just decoration. This is the single biggest surface difference from Lua, and section one is about nothing else. Every example here runs on `pperl`, a Rust reimplementation of Perl 5.42. Each Perl snippet was executed on `pperl`; the `# ...` comments show its real output. Start your scripts with: ```perl use v5.42; ``` That one line turns on `strict` (every variable must be declared with `my`, the cure for Lua's accident-prone implicit globals), `warnings`, the `say` built-in (a `print` with a trailing newline, like Lua's `print`), subroutine signatures, and the modern feature set used throughout this guide. ## How to read this - [Variables and sigils](#variables-and-sigils) - the `$`/`@`/`%` shift, and `local x` becoming `my $x`. - [Strings](#strings) - interpolation, and `..` concatenation becoming `.`. - [Numbers](#numbers) - one surface type, float division. - [Arrays and lists](#arrays-and-lists) - half of your table, 0-based not 1-based. - [Hashes](#hashes) - the other half of your table. - [Context](#context) - the concept Lua has no word for. Read this one. - [Truthiness](#truthiness) - what counts as false, and it differs sharply from Lua. - [Control flow](#control-flow) - `if`, loops, the postfix forms. - [Subroutines](#subroutines) - `function` becomes `sub`; real closures. - [References](#references) - nested data and closures. - [Regular expressions](#regular-expressions) - real regex, not Lua patterns. - [File and stream I/O](#file-and-stream-io) - `open`, line reads, slurp. - [Modules and packages](#modules-and-packages) - `require` becomes `use`. - [Objects](#objects) - metatables become the modern `class` syntax. - [Error handling](#error-handling) - `pcall` becomes `eval` or `try`/`catch`. - [Idioms](#idioms) - the daily one-liners. - [Gotchas coming from Lua](#gotchas-coming-from-lua) - scan before you write. ## Variables and sigils In Lua a name just names a value, whatever its shape. In Perl the **sigil tells you the shape**: `$` for a single value, `@` for an ordered list, `%` for a key/value map. `my` declares a lexically scoped variable, the exact role of Lua's `local`. Where Lua lets a bare assignment silently create a global, `use v5.42` turns that into a compile error: every variable is `my` unless you say otherwise. ```lua local name = "Ada" local langs = {"Lua", "Perl"} local ages = {Ada = 36, Linus = 56} ``` ```perl use v5.42; my $name = "Ada"; my @langs = ("Lua", "Perl"); my %ages = (Ada => 36, Linus => 56); say $name; # Ada say $langs[0]; # Lua say $ages{Ada}; # 36 ``` The gotcha: when you reach *into* an aggregate for one element, the sigil follows the value you get back, not the container. A single element of `@langs` is a scalar, so it is `$langs[0]`, not `@langs[0]`; one value from `%ages` is `$ages{Ada}`. The `@`/`%` form is the whole collection; the `$` form is one piece of it. In Lua the table name `t` never changes whether you write `t`, `t[1]`, or `t.key`; in Perl the leading mark changes with what you are accessing. ## Strings Double quotes interpolate variables, single quotes do not. There is no `string.format` reflex needed for the common case, because the default string already interpolates. The big swap: concatenation is `.`, where Lua uses `..`. ```lua local name = "Ada" local greeting = "Hello, " .. name local shout = string.rep("AB", 3) ``` ```perl use v5.42; my $name = "Ada"; my $greeting = "Hello, $name"; # Hello, Ada my $literal = 'Hello, $name'; # Hello, $name (no interpolation) my $joined = "AB" . "CD"; # ABCD my $shout = "AB" x 3; # ABABAB ``` The gotcha: Lua's concatenation operator `..` does not exist in Perl; the single dot `.` joins strings and the `x` operator repeats them (Lua's `string.rep`). Array elements and hash values interpolate inside double quotes too, so `"First: $langs[0]"` works as written, which removes most of the `..` chaining a Lua developer reaches for. ## Numbers Perl has one numeric type at the surface; you do not pick between integer and float, much as Lua 5.3+ presents a single `number` to most code. Division always produces a float. ```lua print(7 / 2) -- 3.5 print(math.floor(7 / 2)) -- 3 print(2 ^ 10) -- 1024.0 print(10 % 3) -- 1 ``` ```perl use v5.42; say 7 / 2; # 3.5 say int(7 / 2); # 3 say 2 ** 10; # 1024 say 10 % 3; # 1 ``` The gotcha: comparison and equality operators come in two families, and you pick by the *operands' type*, not the variables'. Numbers use `==`, `!=`, `<`, `>`, `<=>`; strings use `eq`, `ne`, `lt`, `gt`, `cmp`. `"10" == "10.0"` is true (numeric), but `"10" eq "10.0"` is false (string). Lua has only `==` and coerces nothing across the string/number line; Perl instead asks *you* to choose the comparison, and using `==` on strings (or `eq` on numbers) is a frequent quiet bug. ## Arrays and lists Here is half of the great table split. A Lua sequence (`{10, 20, 30}`, the table used as an array) becomes a Perl `@array`. The first and loudest difference: **Perl arrays are 0-based**, where Lua arrays conventionally start at index 1. ```lua local t = {"x", "y", "z"} print(t[1]) -- x (Lua: first element is index 1) print(t[3]) -- z print(#t) -- 3 (length) ``` ```perl use v5.42; my @t = ("x", "y", "z"); say $t[0]; # x (Perl: first element is index 0) say $t[2]; # z say $t[-1]; # z (negative indices count from the end) say scalar @t; # 3 (length; @t in scalar context is its size) ``` The mutators are renamed but familiar. `table.insert`/`table.remove` become `push`/`pop`/`shift`/`unshift`: ```perl use v5.42; my @nums = (10, 20, 30); push @nums, 40; # add to the end my $last = pop @nums; # 40, removed from the end say scalar @nums; # 3 say "@nums[0,1]"; # 10 20 (a slice) ``` Lua reaches for explicit `for i = 1, #t do ... end` loops to transform or filter; Perl has `map` (transform) and `grep` (filter), with `$_` as the current element: ```perl use v5.42; my @nums = (1, 2, 3, 4, 5); my @squares = map { $_ * $_ } @nums; # 1 4 9 16 25 my @evens = grep { $_ % 2 == 0 } @nums; # 2 4 my @desc = sort { $b <=> $a } @nums; # 5 4 3 2 1 ``` The gotcha: beyond 0-based indexing, `#t` has no single Perl spelling - you use `scalar @a` for an array's length and `keys %h` for a hash's. And `sort` compares as strings by default, so `sort (10, 9, 100)` gives `10 100 9`. For numbers you must pass a comparator: `sort { $a <=> $b } @nums`. `$a` and `$b` are the two elements being compared, and `<=>` is the numeric three-way operator (`cmp` is its string sibling). ## Hashes Here is the other half of the table split. A Lua table used as a map (`{name = "Ada", age = 36}`, or `t["key"]`) becomes a Perl `%hash`. Declared with `%`, accessed one value at a time with `$h{key}`. ```lua local h = {apple = 3, pear = 5, plum = 2} print(h.apple) -- 3 print(h["pear"]) -- 5 h.plum = nil -- delete a key for k, v in pairs(h) do print(k, v) end ``` ```perl use v5.42; my %h = (apple => 3, pear => 5, plum => 2); say $h{apple}; # 3 say join ",", sort keys %h; # apple,pear,plum say exists $h{pear} ? "yes" : "no"; # yes delete $h{plum}; for my $k (sort keys %h) { say "$k => $h{$k}"; # apple => 3 ... pear => 5 } ``` The gotcha: Lua merges the array and the map into one table and keeps both an array part and a hash part inside it; Perl keeps them strictly apart, so you decide up front whether a thing is an `@array` (integer positions, ordered) or a `%hash` (string keys, unordered). Membership is `exists $h{key}`, the counterpart of Lua's `h[k] ~= nil` test, and the fat comma `=>` is the idiomatic key/value separator that auto-quotes the bareword on its left (so `apple => 3` needs no quotes). A hash has no order, so iterate `sort keys %h` when you want a stable one; Lua's `pairs` is unordered for the same reason. ## Context This is the chapter to read twice. Lua has no general equivalent, and almost every "Perl is weird" story traces back to it. But Lua *does* give you the one bridge that makes it click: **multiple return values**. In Lua, `local a, b = f()` spreads a function's several return values across several variables, and `#t` or `select("#", ...)` collapses a list to a count. Perl generalises that idea into a property of *every* expression. Every expression runs in either **list context** or **scalar context**, decided by where it appears. The same array behaves differently in each: assigned to a *scalar* it gives its length, assigned to a *list* (parenthesised variables) it copies elements. ```perl use v5.42; my @list = (10, 20, 30); my $count = @list; # scalar context -> 3 (the length) say $count; # 3 my ($first) = @list; # list context -> first element copied say $first; # 10 my ($a, $b) = @list; # 10 and 20 say "$a $b"; # 10 20 ``` The parentheses on the left are the whole difference. `my $x = @list` asks "how many?" and gets `3`. `my ($x) = @list` asks "give me the elements" and gets `10`. The list form `my ($a, $b) = @list` is exactly Lua's `local a, b = ...` - the bridge you already know. A subroutine returning several values is the same as a Lua function with multiple `return` values; the caller's context decides what comes back: ```perl use v5.42; sub minmax (@nums) { my @s = sort { $a <=> $b } @nums; return ($s[0], $s[-1]); } my ($lo, $hi) = minmax(4, 1, 9, 2); say "$lo $hi"; # 1 9 ``` A sub can even ask which context it was called in, with `wantarray`, and answer differently - something Lua functions cannot do: ```perl use v5.42; sub items { return wantarray ? (1, 2, 3) : "three items"; } my @got = items(); # list context my $msg = items(); # scalar context say "@got"; # 1 2 3 say $msg; # three items ``` The gotcha: there is no single answer to "what does this array do" - it depends on the surrounding expression, where Lua resolves multiple-value spreading at the specific call site only. When something returns a number you expected to be a list (or vice versa), context is the first suspect. Reach for `scalar` to force a count and parentheses on the left to force element copying. ## Truthiness This is a sharp break from Lua, so slow down. In Lua **only `nil` and `false` are falsy** - the number `0` and the empty string `""` are both *true*. Perl draws the line in a completely different place. Its false values are: the number `0`, the empty string `""`, the string `"0"`, and `undef` (Perl's `nil`). **Everything else is true.** ```perl use v5.42; for my $v (0, 1, "", "0", "00", "0.0", "false") { say "'$v' is ", ($v ? "true" : "false"); } ``` This prints, exactly: ```text '0' is false '1' is true '' is false '0' is false '00' is true '0.0' is true 'false' is true ``` The gotcha: a Lua developer's instinct that `0` is true will silently flip the meaning of every `if ($count)`. In Perl `0` and `""` *are* false, so `if ($n)` is false when `$n` is zero - usually what you want, but the opposite of Lua. Going the other way, `"00"` and `"0.0"` are **true** even though they look numerically zero, because only the exact string `"0"` is false. For a "use this if defined, else a default" choice - Lua's `x = y or default` idiom, which trips on `0` and `false` - use Perl's defined-or `//`, which falls back only on `undef` (Perl's `nil`): ```perl use v5.42; my %cfg = (host => "localhost"); my $port = $cfg{port} // 8080; # key absent -> undef -> 8080 say $port; # 8080 ``` ## Control flow The keywords are familiar; the spelling differs. Lua's `elseif` is Perl's `elsif`, there is no `then`/`do`/`end`, and blocks are braced rather than keyword-delimited. ```lua if n < 0 then sign = "neg" elseif n == 0 then sign = "zero" else sign = "pos" end for _, x in ipairs(nums) do print(x) end while running do step() end ``` ```perl use v5.42; my $sign; if ($n < 0) { $sign = "neg" } elsif ($n == 0) { $sign = "zero" } else { $sign = "pos" } for my $x (@nums) { say $x } while ($running) { step() } ``` Perl adds two things Lua does not have. **Postfix modifiers** let a simple statement carry its own condition or loop, which reads naturally for one-liners: ```perl use v5.42; say $_ for @nums; # loop, one statement say "negative" if $n < 0; # guarded statement $sum += $_ for @nums; # accumulate ``` And `unless`/`until` are the negated forms of `if`/`while`, for when the positive phrasing reads awkwardly: `say "ok" unless $error`. The gotcha: Perl has no `x if c else y` statement; the conditional expression is the C-style ternary with `?`/`:`, which Lua usually fakes with `c and x or y`. The Perl form is direct and chains: `my $sign = $n < 0 ? "neg" : $n == 0 ? "zero" : "pos"`. Loop control is `last`/`next` where Lua has `break` and (in 5.4) `goto continue`. ## Subroutines `function` becomes `sub`. Modern Perl has signatures that name their parameters, including defaults, rather than Lua's positional `...` and manual `arg` handling. ```lua local function greet(name, greeting) greeting = greeting or "Hello" return greeting .. ", " .. name .. "!" end local function total(...) local sum = 0 for _, n in ipairs({...}) do sum = sum + n end return sum end ``` ```perl use v5.42; sub greet ($name, $greeting = "Hello") { return "$greeting, $name!"; } say greet("Ada"); # Hello, Ada! say greet("Linus", "Hi"); # Hi, Linus! sub total (@nums) { my $sum = 0; $sum += $_ for @nums; return $sum; } say total(1, 2, 3, 4); # 10 ``` Lua's named-argument idiom (passing a single table, `f{host=..., port=...}`) maps to a hash parameter, since `key => value` pairs flatten into a list that a `%opts` signature gathers up: ```perl use v5.42; sub connect_to (%opts) { return "host=$opts{host} port=$opts{port}"; } say connect_to(host => "localhost", port => 8080); # host=localhost port=8080 ``` The gotcha: a sub returns the value of its last expression even without `return` (like Lua's explicit-only return, except Perl also has the implicit last-value form), and the return is **context-sensitive** (see [Context](#context)). If you `return @list`, the caller gets the list in list context but the *count* in scalar context - a degree of freedom Lua's multiple-return does not have. ## References Perl's arrays and hashes do not nest directly; you nest them through **references**, a scalar that points at an aggregate. In Lua every table is already a reference (assigning a table shares it, never copies), so this concept is invisible to you there; in Perl you take a reference explicitly with `\`, and follow it with `->`. ```lua local data = {name = "Ada", langs = {"Perl", "Lua"}} print(data.langs[2]) -- Lua table.insert(data.langs, "Ruby") ``` ```perl use v5.42; my $data = { name => "Ada", langs => ["Perl", "Lua"] }; say $data->{name}; # Ada say $data->{langs}[1]; # Lua push $data->{langs}->@*, "Ruby"; # postfix deref to push say "@{$data->{langs}}"; # Perl Lua Ruby ``` `{ ... }` builds an anonymous hash reference and `[ ... ]` an anonymous array reference - the literal building blocks of nested data. The `->` arrow follows a reference one step; between two subscripts it is optional, so `$data->{langs}[1]` and `$data->{langs}->[1]` are the same. Anonymous subs are real closures, exactly like Lua's closures over upvalues - one of the cleanest carry-overs in the whole guide: ```perl use v5.42; sub counter ($start) { my $n = $start; return sub { return $n++ }; } my $c = counter(10); say $c->(); # 10 say $c->(); # 11 say $c->(); # 12 ``` The gotcha: the postfix deref forms (`->@*`, `->%*`, `->$*`) are the modern, readable way to dereference a whole aggregate, and `"@{ ... }"` is how you interpolate a dereferenced array into a string. The older `@$ref` and `${$ref}{key}` forms still work and appear in existing code, but reach for the arrow and postfix forms in new code. ## Regular expressions This is an upgrade, not a translation. Lua has **patterns** - `string.match`, `string.gsub`, character classes like `%d` - which are deliberately *not* regular expressions and lack alternation, quant, and backreferences. Perl has full regex as **syntax**: `=~` binds a pattern to a string, `m//` matches, `s///` substitutes. Capture groups land in `$1`, `$2`, and named captures in `%+`. ```lua local s = "2026-06-20" local y, m, d = string.match(s, "(%d+)-(%d+)-(%d+)") local t = string.gsub("a,b,c", ",", ";") -- a;b;c ``` ```perl use v5.42; my $s = "2026-06-20"; if ($s =~ /(\d{4})-(\d{2})-(\d{2})/) { say "$1 $2 $3"; # 2026 06 20 } if ($s =~ /(?\d{4})/) { say $+{year}; # 2026 } (my $t = "a,b,c") =~ s/,/;/g; # a;b;c my @parts = split /,/, "one,two,three"; # one two three ``` A `/g` match in list context returns *all* the matches, the direct analogue of looping `string.gmatch`: ```perl use v5.42; my @nums = "x1y2z3" =~ /(\d)/g; # 1 2 3 say "@nums"; ``` The gotcha: the metacharacters differ from Lua patterns - Perl uses `\d` where Lua uses `%d`, `*`/`+`/`?` quantifiers, `|` alternation, and `(?...)` named groups, none of which Lua patterns support. And `s///` modifies its target *in place* and returns the number of substitutions, not a new string, where Lua's `gsub` returns a new string. That is why the example copies first with `(my $t = "a,b,c")` and then substitutes on the copy. To get a new string without touching the original, use the `/r` flag: `my $t = $s =~ s/,/;/gr`. ## File and stream I/O The idiom is the three-argument `open` with a lexical filehandle, the counterpart of Lua's `io.open`. The angle-bracket operator `<$fh>` reads a line, the role of Lua's `fh:read("l")` or the `fh:lines()` iterator. ```lua local f = io.open("/tmp/sample.txt", "w") for i = 1, 3 do f:write("line " .. i .. "\n") end f:close() f = io.open("/tmp/sample.txt", "r") for line in f:lines() do print("got:", line) end f:close() ``` ```perl use v5.42; my $path = "/tmp/sample.txt"; open my $wh, '>', $path or die "cannot write: $!"; print {$wh} "line $_\n" for 1 .. 3; close $wh; open my $rh, '<', $path or die "cannot read: $!"; while (my $line = <$rh>) { chomp $line; say "got: $line"; # got: line 1 ... got: line 3 } close $rh; ``` To slurp every line at once, read the handle in list context: ```perl use v5.42; open my $fh, '<', "/tmp/sample.txt" or die $!; my @lines = <$fh>; # all lines, including newlines say scalar @lines; # 3 ``` The gotcha: a line read with `<$fh>` keeps its trailing newline (Lua's `fh:lines()` strips it, `fh:read("L")` keeps it); call `chomp` to strip it. And `open` returns false on failure rather than raising, much like Lua's `io.open` returning `nil, err`, so the `or die "...: $!"` idiom is mandatory - `$!` is the system error message, Perl's `errno`. ## Modules and packages Lua's `require` becomes Perl's `use`. The namespace separator is `::`, not `.`, and a module `Foo::Bar` lives at `Foo/Bar.pm` on the include path `@INC` (Lua's `package.path`). Unlike Lua's `require`, which returns a table you assign yourself, `use` imports selected names directly into your namespace. ```lua local sqrt = math.sqrt local sum = 0 for i = 1, 10 do sum = sum + i end ``` ```perl use v5.42; use List::Util qw(sum0 max first); say sum0(1 .. 10); # 55 say max(3, 7, 2); # 7 say first { $_ % 2 == 0 } (1, 3, 4, 5); # 4 ``` Here is a pleasant surprise coming from Lua's deliberately tiny standard library: Perl ships a large built-in set, and much of what you would hand-roll or pull from LuaRocks is in core or one `use` away on CPAN, the Perl package archive. `List::Util`, `Scalar::Util`, `POSIX`, `File::Spec`, and `Data::Dumper` are the everyday ones. The gotcha: `use` runs at *compile time*, not at the line where it appears, so its placement relative to your code does not matter the way a `require` call's does. Importing is opt-in per name: `use List::Util qw(sum0 max)` brings exactly `sum0` and `max` into your namespace, nothing else. ## Objects Lua has no built-in object system; you build one from tables and metatables - `setmetatable(t, {__index = Class})` for method lookup and inheritance, `__add`/`__eq` for operators. Perl 5.42 gives you a first-class `class` syntax instead, so the common case needs no metatable wiring. Fields are declared with `field`, methods with `method`, and `$self` is available inside every method (you do not pass it as the first parameter the way Lua's `self` / colon-call does). ```lua local Point = {} Point.__index = Point function Point.new(x, y) return setmetatable({x = x or 0, y = y or 0}, Point) end function Point:to_string() return "(" .. self.x .. ", " .. self.y .. ")" end function Point:move(dx, dy) self.x = self.x + dx self.y = self.y + dy return self end local p = Point.new(3, 4) print(p:to_string()) -- (3, 4) p:move(1, 1) print(p:to_string()) -- (4, 5) ``` ```perl use v5.42; use feature 'class'; class Point { field $x :param = 0; field $y :param = 0; method to_string { return "($x, $y)" } method move ($dx, $dy) { $x += $dx; $y += $dy; return $self; } } my $p = Point->new(x => 3, y => 4); say $p->to_string; # (3, 4) $p->move(1, 1); say $p->to_string; # (4, 5) ``` The constructor is generated for you, replacing Lua's hand-written `Point.new`: `:param` marks a field that the auto-built `new` accepts as a named argument, and `= 0` is its default. Inheritance, which Lua builds from a chain of `__index` metatables, is the `:isa` attribute: ```perl use v5.42; use feature 'class'; class Point3D :isa(Point) { field $z :param = 0; method z { return $z } } my $q = Point3D->new(x => 1, y => 2, z => 3); say $q->to_string, " z=", $q->z; # (1, 2) z=3 ``` The gotcha: the `class` feature is still marked experimental in Perl 5.42, and on `pperl` the "class is experimental" notices print to **stderr** even when you try to silence them. They do not touch your program's output, so a script that prints to stdout behaves exactly as shown above; just expect the notices on the error stream. Lua's operator metamethods (`__add`, `__eq`, and friends) map to Perl's `overload` pragma rather than to `class`; for that, the older `bless`-based object model, roles, and inheritance details, see the [Object-oriented Programming guide](../oop/index). ## Error handling Lua's protected call `pcall(f)` - which returns `false, err` instead of unwinding - has two Perl counterparts. The classic one wraps code in `eval { }` and inspects `$@` (the caught error) afterward: ```lua local ok, err = pcall(function() risky(-1) end) if not ok then print("caught: " .. err) end ``` ```perl use v5.42; my $result = eval { risky(-1) }; if ($@) { print "caught: $@"; # caught: negative! } ``` The modern form is `try`/`catch`, which reads more like a conventional exception block than `pcall`'s boolean-return dance: ```perl use v5.42; use feature 'try'; try { risky(-4); } catch ($e) { print "caught: $e"; # caught: negative! } ``` where the raising side is `die`, Perl's `error`: ```perl use v5.42; sub risky ($n) { die "negative!\n" if $n < 0; return sqrt $n; } ``` The gotcha: `die` with a string that ends in `\n` produces exactly that message; `die` with a string that does *not* end in a newline gets `" at SCRIPT line N.\n"` appended automatically (Lua's `error` does a similar position-prefixing unless you pass level `0`). End your error strings with `\n` when you want a clean message and omit it when you want the file-and-line trace. Note also the two error variables: `$@` is the last caught exception, while `$!` is the last *system* error (a failed `open`, the `errno` string). ## Idioms A few patterns you will see daily and want in your fingers. Sort a list by a computed key (the Schwartzian transform - decorate, sort, undecorate in one pipeline, the Perl reflex where Lua passes a comparator function to `table.sort`): ```perl use v5.42; my @words = qw(apple fig pear); my @by_length = map { $_->[1] } sort { $a->[0] <=> $b->[0] } map { [ length $_, $_ ] } @words; say "@by_length"; # fig pear apple ``` Build a hash from a list, turning a sequence into a lookup table in one step: ```perl use v5.42; my @words = qw(apple pear fig); my %len = map { $_ => length $_ } @words; say "$_ => $len{$_}" for sort keys %len; # apple => 5 # fig => 3 # pear => 4 ``` And `pperl` is a command-line tool in the `awk`/`sed` niche, a role Lua does not target. The implicit-loop one-liner is the Perl developer's everyday reflex: ```bash pperl -lane '$s += $F[-1]; END { print $s }' data.txt ``` The gotcha: read the [One-Liners guide](../oneliner/index) for the switches (`-n`, `-p`, `-a`, `-l`, `-e`) and the dozens of recipes built on them; it is one of the highest-leverage things to learn coming from a scripting background where this niche was someone else's job. ## Gotchas coming from Lua The traps, collected for a final scan before you start writing: - **One table becomes two types.** A Lua table is an `@array` (integer-keyed, ordered) *or* a `%hash` (string-keyed, unordered) in Perl, never both at once. Decide which you have before you reach for it. - **Arrays are 0-based.** Lua's `t[1]` is Perl's `$t[0]`; the last element is `$t[-1]`. This is the most common porting off-by-one. - **Sigils change with usage.** One element of `@a` is `$a[0]`; one value of `%h` is `$h{k}`. The container sigil and the element sigil differ - the bare Lua name `t` has no such shift. - **Concatenation is `.`, not `..`.** Lua's `..` does not exist; `.` joins strings and `x` repeats them. - **Truthiness is inverted at zero.** In Lua `0` and `""` are *true*; in Perl they are **false**. Only `0`, `""`, `"0"`, and `undef` are false, so `"00"` and `"0.0"` are surprisingly **true**. - **Two comparison families.** `==`/`<`/`<=>` for numbers, `eq`/`lt`/`cmp` for strings. Pick by operand type; Lua's single `==` has no equivalent split. - **`sort` is string-sorted by default.** Pass `{ $a <=> $b }` for numeric order. - **Context.** `my $x = @a` is the *length*; `my ($x) = @a` is the *first element*. The list form is your friend `local a, b = f()`; the scalar form is new. Re-read [Context](#context) when a value comes back the wrong shape. - **Regex is not Lua patterns.** Perl has full regex (`\d`, `|`, `*`, named groups); Lua patterns (`%d`, no alternation) are a different, smaller language. `s///` edits in place and returns a count; use `/r` for a new string. - **`open` does not raise.** Use `open ... or die "...: $!"`, the way Lua's `io.open` returns `nil, err`. - **`chomp` your line reads.** `<$fh>` keeps the trailing newline that Lua's `lines()` would have stripped. - **Non-ASCII source needs `use utf8;`.** Under `use v5.42;` a literal non-ASCII byte in your source is a compile error without it. Keep string data ASCII unless you specifically add `use utf8;`. - **The `class` feature prints experimental notices to stderr** on `pperl`. Correct output, noisy error stream. Operator metamethods map to the `overload` pragma, not to `class`.