Perl has three fundamental variable types, each identified by a sigil — a punctuation character at the start of the name. $ means "one thing" (scalar). @ means "a list of things" (array). % means "a dictionary of key-value pairs" (hash). The sigil is part of the syntax, not the name — the variable named fruit can exist in all three forms at once.
#!/usr/bin/perl use strict; use warnings; # $ sigil — ONE value (a scalar) my $name = "Alice"; # holds a string my $age = 30; # holds a number my $pi = 3.14159; # holds a float my $empty; # holds undef (no value) # @ sigil — an ORDERED LIST (array) my @colors = ("red", "green", "blue"); my @numbers = (1, 2, 3, 4, 5); my @empty_list = (); # empty array # % sigil — KEY-VALUE PAIRS (hash / dictionary) my %person = ( name => "Bob", # => is the "fat comma" — same as "," age => 25, city => "London", ); # Accessing values — sigil changes based on WHAT you get back print $name; # scalar — stays $ print $colors[0]; # ONE element from array — $ (not @) print $person{name}; # ONE value from hash — $ (not %)
$. You're getting one thing back. $colors[0] not @colors[0].=> is just a comma that auto-quotes the left side. name => "Bob" is identical to "name", "Bob". It makes hash assignments read like key-value pairs.undef. It's false in boolean context, "" in string context, and 0 in numeric context. Check with defined($var).my declares a lexical variable — it only exists in the current block { } or file. Always use my. Never use undeclared globals.Perl has two main string delimiters with completely different behaviour. Double quotes " " interpret escape sequences and interpolate variables — variables inside them are expanded to their values. Single quotes ' ' are completely literal — no escapes (except \' and \\), no interpolation. This is the most important beginner distinction in Perl.
my $name = "Alice"; my $age = 30; # Double quotes: variables ARE expanded, \n IS a newline print "Hello, $name! You are $age years old.\n"; # Output: Hello, Alice! You are 30 years old. # Single quotes: everything is LITERAL — \ is just a backslash print 'Hello, $name! \n'; # Output: Hello, $name! \n (literally) # String concatenation uses the . (dot) operator my $greeting = "Hello" . ", " . $name; # "Hello, Alice" # String repetition uses the x operator my $line = "-" x 20; # "--------------------" my $stars = "*" x 5; # "*****" # Common string functions my $s = " Hello, World! "; length($s); # 18 — number of characters uc($s); # " HELLO, WORLD! " — uppercase lc($s); # " hello, world! " — lowercase ucfirst("hello"); # "Hello" index($s, "World"); # 9 — position (0-based), -1 if not found substr($s, 2, 5); # "Hello" — start at 2, length 5 # Trim whitespace (no built-in — use a substitution) $s =~ s/^\s+|\s+$//g; # now "Hello, World!" # Heredoc — multiline string (useful for templates) my $block = <<END; Line one Line two: $name Line three END
$var expands to its value. @arr expands to the array joined by spaces. To avoid interpolation: use single quotes, or escape with backslash: \$name.\n newline · \t tab · \r carriage return · \\ literal backslash · \" literal double quote · \0 null byte. (dot) for string joining — NOT + like JavaScript. "Hello" + " World" gives 0 (numeric addition of two non-numeric strings).<<END interpolates variables (like double quotes). <<'END' is literal (like single quotes). The closing word must be at column 1, alone on its line."$hash{key}" works fine in strings, but "@array" joins all elements with spaces. To embed a single element use "$array[0]". To embed a hash value use "$hash{key}".
Perl stores numbers internally as integers or floating-point and converts automatically. There's no separate int/float/double distinction — a scalar holds whatever the number is. Perl also has two completely separate sets of comparison operators: one for numbers and one for strings. Using the wrong one is a common bug.
# Numeric literals my $int = 42; my $float = 3.14; my $neg = -7; my $sci = 1.5e6; # 1,500,000 my $big = 1_000_000; # _ separators: ignored, for readability my $hex = 0xFF; # 255 (0x prefix = hexadecimal) my $oct = 0755; # 493 (0 prefix = octal!) my $bin = 0b1010; # 10 (0b prefix = binary) # Arithmetic operators my $sum = 10 + 3; # 13 addition my $diff = 10 - 3; # 7 subtraction my $prod = 10 * 3; # 30 multiplication my $quot = 10 / 3; # 3.33 division (float) my $mod = 10 % 3; # 1 modulo (remainder) my $pow = 2 ** 8; # 256 exponentiation (power) # Increment / Decrement $sum++; # $sum += 1 — post-increment $sum--; # $sum -= 1 — post-decrement $sum += 5; # compound assignment: $sum = $sum + 5 # NUMERIC comparison (returns true/false) 10 == 10; # equal 10 != 5; # not equal 10 > 5; # greater than 10 < 20; # less than 10 >= 10; # greater than or equal # STRING comparison (uses alphabetical/ASCII order) "abc" eq "abc"; # equal "abc" ne "xyz"; # not equal "abc" lt "xyz"; # less than (alphabetically) "abc" gt "aaa"; # greater than # Spaceship operators — return -1, 0, or 1 (for sorting) 5 <=> 3; # 1 (numeric spaceship) "b" cmp "a"; # 1 (string spaceship)
== != < > for numbers. Use eq ne lt gt for strings. "10" == "10.0" is true (numeric). "10" eq "10.0" is false (string).** for powers: 2**10 is 1024. Not the caret ^ — which is bitwise XOR.010 == 8, not 10. This surprises everyone. Use oct("010") to convert strings, oct("0x1f") for hex strings.| Numeric | String equivalent | Meaning |
|---|---|---|
| == | eq | equal |
| != | ne | not equal |
| < | lt | less than |
| > | gt | greater than |
| <= | le | less than or equal |
| >= | ge | greater than or equal |
| <=> | cmp | three-way compare (for sort) |
Arrays hold an ordered list of scalars, indexed from 0. They grow and shrink automatically. Perl provides built-in functions like push, pop, shift, unshift, splice, sort, reverse, grep, and map — mastering these eliminates most hand-written loops.
my @fruits = ("apple", "banana", "cherry"); # Access by index — ALWAYS use $ because you get one thing back print $fruits[0]; # "apple" (first element) print $fruits[-1]; # "cherry" (last element — negative indexes) print $fruits[-2]; # "banana" (second-to-last) # Length — use scalar() or $# (last index) my $count = scalar(@fruits); # 3 — number of elements my $last_index = $#fruits; # 2 — last valid index (count - 1) # push/pop — add/remove from the END push @fruits, "date"; # ("apple","banana","cherry","date") my $last = pop @fruits; # removes and returns "date" # shift/unshift — add/remove from the FRONT unshift @fruits, "avocado"; # ("avocado","apple","banana","cherry") my $first = shift @fruits; # removes and returns "avocado" # sort and reverse my @sorted = sort @fruits; # alphabetical my @nums = sort { $a <=> $b } (5,2,8,1); # numeric sort my @rev = reverse @sorted; # grep — filter (like WHERE in SQL) my @long = grep { length($_) > 5 } @fruits; # map — transform (like SELECT in SQL) my @upper = map { uc($_) } @fruits; # join — turn array into a single string my $csv = join(",", @fruits); # "apple,banana,cherry" my $spaced = join(" ", @fruits); # "apple banana cherry" # split — turn a string into an array my @parts = split(/,/, "a,b,c"); # ("a","b","c") my @words = split(/\s+/, "hello world foo"); # Array slices — multiple elements at once my @two = @fruits[0, 2]; # elements 0 and 2 — note @ not $ my @range = @fruits[0..1]; # elements 0 to 1 (range operator)
grep { } and map { }, the current element is in $_. Many Perl functions use $_ as their implicit argument. You'll see $_ everywhere in Perl code.push/pop work on the right end (fast). shift/unshift work on the left end (slower — shifts all indices). Use push/pop for stacks.sort { }, Perl provides $a and $b — the two elements being compared. Return negative, 0, or positive. $a <=> $b sorts ascending numerically.@array[0, 2] (with @). You're getting a list back, so the sigil changes to @.A hash is Perl's key-value dictionary. Keys are always strings; values are any scalar. Hash order is not guaranteed — don't rely on insertion order. Use keys, values, and each to iterate. The exists and delete functions test and remove entries.
my %person = ( name => "Carol", age => 28, city => "Paris", ); # Read a value — use $ because you get ONE thing back print $person{name}; # "Carol" print $person{age}; # 28 # Add a new key or modify existing $person{email} = "carol@example.com"; $person{age} = 29; # update # Check if a key exists (don't just test the value!) if (exists $person{email}) { print "email is set\n"; } # Delete a key delete $person{city}; # List all keys (order is RANDOM each run) my @k = keys %person; # ("name","age","email") in some order my @v = values %person; # ("Carol",29,"carol@...") same order as keys # Iterate with for — sort keys for consistent order for my $key (sort keys %person) { print "$key: $person{$key}\n"; } # Iterate with while/each (key-value at once) while (my ($k, $v) = each %person) { print "$k = $v\n"; } # Count entries my $count = scalar(keys %person); # 3 # Hash slice — get multiple values at once my ($name, $age) = @person{qw(name age)}; # @ sigil for multiple
exists $h{k} asks "is there a key called k?" — even if value is undef. defined $h{k} asks "is the value not undef?" Use exists to check key presence.qw(name age city) is shorthand for ("name", "age", "city") — splits on whitespace, no commas needed. Great for lists of strings.sort keys %hash if you need consistent output.$h{a}{b} = 1 creates %h, the inner hashref $h{a}, and the key b — all at once.Context is one of Perl's most distinctive features. The same expression returns different things depending on where it's used. An array in scalar context returns its element count. An array in list context returns all its elements. Many functions also behave differently based on context. Understanding this is essential for reading Perl code correctly.
my @arr = ("a", "b", "c"); # SCALAR context — @arr evaluates to its COUNT my $n = @arr; # $n = 3 my $n2 = scalar(@arr); # explicit scalar context if (@arr) { ... } # boolean (true if array not empty) print "count: " . @arr; # . forces scalar: "count: 3" # LIST context — @arr returns all elements my ($first, $second) = @arr; # $first="a", $second="b" my @copy = @arr; # @copy = ("a","b","c") # localtime in scalar context = formatted string my $time_str = localtime; # "Mon Jan 1 12:00:00 2024" # localtime in list context = individual components my ($sec, $min, $hour, $day, $mon, $year) = localtime; # wantarray() — test which context YOUR sub was called in sub flexible { if (wantarray) { return (1, 2, 3); # list context: return list } else { return "summary"; # scalar context: return string } } my $s = flexible(); # "summary" my @l = flexible(); # (1, 2, 3)
@arr), different questions, different answers.
$scalar =, using in if()/while(), using with . concatenation, calling scalar() explicitly, or any arithmetic.@array = or my ($a, $b) =, passing to a function that expects a list, using in for/foreach.Perl's conditionals are very readable. Besides the standard if/elsif/else, Perl has unless (if-not), postfix forms (condition after statement), and the ternary operator. Truth in Perl: false is 0, "0", "", undef. Everything else — including "0.0", "false", and " " — is true.
my $score = 75; # Standard if / elsif / else if ($score >= 90) { print "A\n" } elsif ($score >= 80) { print "B\n" } elsif ($score >= 70) { print "C\n" } # fires: score is 75 else { print "F\n" } # unless = "if NOT" — reads naturally in English unless ($score < 60) { print "Passed!\n"; } # Ternary operator: condition ? if_true : if_false my $label = ($score >= 60) ? "pass" : "fail"; # Postfix forms — condition AFTER the statement (very Perlish) print "High score!\n" if $score > 90; print "Not perfect\n" unless $score == 100; # Defined-or operator // — use default if value is undef my $name = undef; my $label2 = $name // "Guest"; # "Guest" (because $name is undef) $name //= "Guest"; # assign only if currently undef # Boolean operators: && and || (or 'and' / 'or' in words) my $valid = ($score > 0) && ($score <= 100); my $ok = ($score >= 90) || ($score == 42); # 'or' and 'and' are the same but lower precedence (for flow control) open(my $fh, '<', 'file.txt') or die "Can't open: $!\n";
0 · "0" · "" (empty string) · undef · empty list (). The string "false" is TRUE. "0.0" is TRUE. " " (space) is TRUE.// (defined-or) tests if the value is defined. || tests if it's truthy. Use // when 0 or "" are valid values you want to keep.do_thing() if $condition is idiomatic Perl for simple one-liners. It reads like English. Use it for guards: return if $error, next if $skip.open(...) or die "..." is the standard error-checking idiom. If open returns false (failure), die runs. The $! variable holds the system error message.Perl has four loop types. foreach (or for — they're identical) iterates over a list. while loops while a condition is true. until loops until a condition becomes true. C-style for gives fine-grained control. last, next, and redo control loop flow — Perl's equivalents of break, continue, and restart.
# foreach — iterate over a list (most common) foreach my $fruit ("apple", "banana", "cherry") { print "$fruit\n"; } # for and foreach are interchangeable in Perl for my $i (1..5) { # 1..5 is the range operator print "$i\n"; # prints 1 2 3 4 5 } # Default variable $_ — no explicit loop variable needed for (qw(red green blue)) { print "$_\n"; # $_ holds the current element } # C-style for loop for (my $i = 0; $i < 5; $i++) { print "$i\n"; # init; condition; increment } # while — loop while condition is true my $count = 3; while ($count > 0) { print "$count\n"; $count--; } # until — loop UNTIL condition becomes true (while NOT) $count = 0; until ($count >= 3) { print "$count\n"; $count++; } # Loop control for my $n (1..10) { next if $n % 2 == 0; # skip even numbers (next = continue) last if $n > 7; # stop when n > 7 (last = break) print "$n\n"; # prints 1, 3, 5, 7 } # Postfix for — one-liner iteration print "$_\n" for (1..3); # prints 1, 2, 3 # Reading a file line by line (canonical while loop pattern) while (my $line = <$fh>) { chomp $line; # remove trailing newline print $line; }
last = break out of the loop. next = skip to next iteration. redo = restart current iteration without re-testing the condition. All work with postfix conditions too.1..10 creates a list (1,2,3...10). Works in for loops, array assignment, and slices. Also works for strings: 'a'..'z'.$_ is an alias to the actual list element — modifying $_ modifies the original. To avoid this, use for my $item (@list).Subroutines (functions) are defined with sub. All arguments arrive in the special array @_ — always unpack them at the top of the sub. The last evaluated expression is returned automatically, but explicit return is clearer. Perl subs can return multiple values naturally as a list.
# Basic sub definition sub greet { my ($name) = @_; # @_ holds ALL arguments return "Hello, $name!"; } print greet("World"); # "Hello, World!" # Multiple arguments sub add { my ($a, $b) = @_; return $a + $b; } # Default parameter values using //= sub greet2 { my ($name, $title) = @_; $title //= "friend"; # use "friend" if $title is undef return "Hello, $title $name!"; } print greet2("Alice"); # "Hello, friend Alice!" print greet2("Bob", "Dr."); # "Hello, Dr. Bob!" # Returning multiple values (as a list) sub min_max { my @nums = @_; my $min = $nums[0]; my $max = $nums[0]; for my $n (@nums) { $min = $n if $n < $min; $max = $n if $n > $max; } return ($min, $max); # return TWO values } my ($lo, $hi) = min_max(5,2,9,1); # $lo=1, $hi=9 # Named parameters via a hash (useful for many args) sub create_user { my (%args) = @_; # unpack @_ as a hash $args{role} //= "user"; return \%args; } my $u = create_user(name=>"Eve", age=>22);
f(1, 2, 3), inside f, @_ = (1,2,3). If you pass an array f(@arr), its elements are flattened into @_. To pass an array without flattening, pass a reference: f(\@arr).return, a Perl sub returns the value of the last expression evaluated. This is fine for simple getters but use explicit return in complex subs — it's clearer and prevents surprises.my (%args) = @_ turns argument pairs into a hash. Call it as f(key=>"val", key2=>"val2"). This is the cleanest way to handle subs with many optional parameters.use strict, it's good practice to define subs before using them or forward-declare them.A reference is a scalar that points to another variable. References are how Perl builds complex data structures (arrays of hashes, hashes of arrays) and how you pass arrays without flattening them. Use \ to create a reference, use -> to dereference it.
# Creating references with backslash \ my @arr = (1,2,3); my $aref = \@arr; # ref to existing array my %hash = (a=>1, b=>2); my $href = \%hash; # ref to existing hash # Anonymous refs — create without naming the original my $aref2 = [10, 20, 30]; # [ ] creates anonymous arrayref my $href2 = { x=>1, y=>2 }; # { } creates anonymous hashref # Accessing via -> (arrow/dereference operator) print $aref2->[0]; # 10 — element 0 print $href2->{x}; # 1 — key "x" # Nested structures — array of hashes (common pattern) my @people = ( { name => "Alice", age => 30 }, # hashref { name => "Bob", age => 25 }, ); # Access nested: $array[index]->{key} print $people[0]->{name}; # "Alice" print $people[1]->{age}; # 25 # Iterate array of hashes for my $p (@people) { print "$p->{name} is $p->{age}\n"; } # ref() tells you what kind of reference something is print ref($aref); # "ARRAY" print ref($href); # "HASH" print ref(\$aref); # "REF" (reference to a reference)
( ) creates a list — context-dependent. [ ] creates an anonymous arrayref — always returns a scalar reference. Use [ ] when building nested structures.{ } is either a hash ref or a code block depending on context. To force hash ref: +{ key => val }. To force block: put a semicolon first: {; key => val }.$a->[0]{key} is the same as $a->[0]->{key}. The first arrow is always required.ref($x) eq 'ARRAY' ensures it's safe to use @{$x}. On non-references, ref() returns "".Perl's regex support is legendary — it's where the =~ operator, capture groups, global matches, and the s/// substitution all live. The pattern is delimited by / slashes. Capture groups use ( ). Results land in $1, $2, etc. The /g modifier finds all matches.
my $text = "The price is $42.50 today"; # =~ tests if a string matches a pattern if ($text =~ /price/) { print "Found 'price'\n"; } # Capture groups ( ) — results in $1, $2, $3... if ($text =~ /\$(\d+\.\d+)/) { print "Amount: $1\n"; # "42.50" } # !~ tests if something does NOT match if ($text !~ /banana/) { print "No banana here\n"; } # Common modifiers # /i — case-insensitive # /g — global (find all matches) # /m — multiline (^ and $ match line boundaries) # /x — extended (ignore whitespace, allow comments) $text =~ /PRICE/i; # matches "price" case-insensitively # Global match — capture all occurrences into an array my $html = "<b>bold</b> and <i>italic</i>"; my @tags = ($html =~ /<(\w+)>/g); # @tags = ("b", "b", "i", "i") — all tag names # Substitution: s/pattern/replacement/ my $str = "Hello World"; $str =~ s/World/Perl/; # "Hello Perl" (first match) # Global substitution /g — replace all occurrences my $s2 = "aaa bbb aaa"; $s2 =~ s/aaa/xxx/g; # "xxx bbb xxx" # Common metacharacters # . any character except newline # \d digit [0-9] \D non-digit # \w word char [a-z0-9_]\W non-word # \s whitespace \S non-whitespace # ^ start of string $ end of string # + one or more * zero or more # ? zero or one {n} exactly n times # | alternation (or) [abc] character class
/x modifier to write readable regex with whitespace and comments: s/ ^\s+ | \s+$ //gx is much clearer than s/^\s+|\s+$//g for trimming.
$1, $2... are only valid until the next regex match. Always copy them immediately: my $val = $1.(my $copy = $str) =~ s/old/new/g. The parentheses copy first, then apply the substitution.Always use the three-argument form of open and always check for errors with or die. The mode strings are: '<' read, '>' write (create/overwrite), '>>' append. The <$fh> diamond operator reads one line at a time. chomp removes the trailing newline.
# Write a file — '>' creates/overwrites open(my $out, '>', 'myfile.txt') or die "Cannot open for writing: $!\n"; # $! holds the OS error message on failure print $out "First line\n"; print $out "Second line\n"; close($out); # always close when done # Read a file line by line — most memory-efficient open(my $in, '<', 'myfile.txt') or die "Cannot open: $!\n"; while (my $line = <$in>) { chomp $line; # remove trailing \n print "Line: $line\n"; } close($in); # Read entire file into an array (one line per element) open(my $fh, '<', 'myfile.txt') or die $!; my @lines = <$fh>; # list context: reads all lines close($fh); chomp @lines; # chomp all elements at once # Append to a file — '>>' keeps existing content open(my $app, '>>', 'myfile.txt') or die $!; print $app "Third line\n"; close($app); # Test operators — check file attributes if (-e 'myfile.txt') { print "exists\n" } if (-f 'myfile.txt') { print "is a file\n" } if (-d '/tmp') { print "is a dir\n" } if (-r 'myfile.txt') { print "is readable\n" } my $sz = -s 'myfile.txt'; # file size in bytes
open(FH, ">file") is unsafe — if a filename starts with >, the mode is ambiguous. Always use 3-arg: open($fh, '>', $filename). This separates mode from filename cleanly.$! holds the operating system error string (like "No such file or directory") after a failed system call. Always include it in die messages: die "Can't open: $!".chomp removes the trailing newline (if present) — safe to call even if there's no newline. chop removes the last character unconditionally — rarely what you want.my $fh) close automatically when they go out of scope. Old-style bareword handles (FH) are global and don't auto-close. Always use my $fh.Every Perl file should start with these two lines. No exceptions. use strict requires all variables to be declared with my (catching typos before they become bugs). use warnings enables helpful diagnostic messages about suspicious operations. Together they catch a huge class of beginner and expert bugs at compile time.
#!/usr/bin/perl use strict; # ALWAYS — require declared variables use warnings; # ALWAYS — enable diagnostic messages # Without strict, this silently creates a new global $nme (typo!): # $nme = "Alice"; # oops! $name was intended # With strict, this fails at compile time with "Global symbol... # requires explicit package name" — you catch the bug immediately # With strict, you MUST use my (or our for globals, local for dynamic) my $name = "Alice"; # correct # use warnings catches these common problems: # 1. Using undef in a numeric context my $x; my $y = $x + 1; # warns: "Use of uninitialized value" # 2. Comparing string with == (numeric operator) if ("hello" == "world") {} # warns: both treated as 0 # 3. Useless use of a variable in void context $name; # warns: "Useless use of $name in void context" # Suppressing a specific warning when you know it's safe: { no warnings 'uninitialized'; my $val = $x . "suffix"; # no warning in this block } # For modern Perl (5.10+), add these too: use feature 'say'; # enables say() — print with auto-newline use feature 'state'; # enables state variables (persistent in subs) # Or all in one: use 5.010; # enables all features from Perl 5.10 onwards use 5.020; # enables all features from Perl 5.20 onwards
#!/usr/bin/perl · use strict; · use warnings;. If you see Perl code without these, treat it with suspicion — it may have hidden bugs.
my, our, or local for every variable. Typos in variable names become compile-time errors, not silent wrong-value bugs.$$name where $name = "x" would access $x without strict.say "hello" is print "hello\n" with an automatic newline appended. Available from Perl 5.10 with use feature 'say' or use 5.010.Perl has dozens of special punctuation variables. Most have long English aliases via use English. Here are the ones you'll encounter constantly. They're documented fully in perlvar.
# $_ — the default variable (used by loops, grep, map, etc.) for (qw(a b c)) { print; # print $_ — print without argument uses $_ } # @_ — arguments to the current subroutine sub show_args { print "Got: " . join(", ", @_) . "\n"; } # $! — OS error string after failed system call open(my $fh, '<', 'missing.txt') or print "Error: $!\n"; # Error: No such file or directory # $@ — error from last eval { } block eval { die "something failed\n" }; print "Caught: $@" if $@; # $0 — name of the current script print "Running: $0\n"; # $$ — process ID of current Perl process print "PID: $$\n"; # @ARGV — command-line arguments to the script # perl script.pl arg1 arg2 --> @ARGV = ("arg1","arg2") if (@ARGV) { print "First arg: $ARGV[0]\n"; } # %ENV — environment variables print "Home: $ENV{HOME}\n"; print "Path: $ENV{PATH}\n"; # $/ — input record separator (default: newline) # Setting $/ to undef makes <$fh> read entire file at once (slurp mode) my $entire_file; { local $/ = undef; # temporarily unset (local = restore after block) open(my $f, '<', 'myfile.txt') or die $!; $entire_file = <$f>; # reads entire file } # $, — output field separator (between print args, default: none) # $\ — output record separator (after each print, default: none) { local $, = ", "; # print "a","b","c" ==> "a, b, c" local $\ = "\n"; # auto-append newline to every print print "x", "y", "z"; }
| Variable | Long name (use English) | What it holds |
|---|---|---|
| $_ | $ARG | Default input / loop variable |
| @_ | — | Sub argument list |
| $! | $OS_ERROR | OS error from last failed syscall |
| $@ | $EVAL_ERROR | Error from last eval block |
| $0 | $PROGRAM_NAME | Script filename |
| $$ | $PID / $PROCESS_ID | Current process ID |
| @ARGV | — | Command-line arguments |
| %ENV | — | Environment variables |
| $/ | $INPUT_RECORD_SEPARATOR | Line ending for reading |
| $, | $OUTPUT_FIELD_SEPARATOR | Separator between print args |
| $\ | $OUTPUT_RECORD_SEPARATOR | Appended after each print |
| $1 $2... | — | Regex capture groups |
| #fruits | — | Last index of @fruits |
local — not my — when temporarily changing special variables like $/, $,, $\. local saves the old value and restores it when the block exits, even if an error occurs. my creates a new variable that shadows the global without restoring it.