Skip to content

Scalars, Strings, and Numbers

Perl's Building Blocks: One Thing at a Time

Version: 1.1 Year: 2026


Copyright (c) 2025-2026 Ryan Thomas Robson / Robworks Software LLC. Licensed under CC BY-NC-ND 4.0. You may share this material for non-commercial purposes with attribution, but you may not distribute modified versions.


Variables and Sigils

Every variable in Perl starts with a punctuation character called a sigil. The sigil tells you what kind of data the variable holds:

Sigil Type Example
$ Scalar (single value) $name
@ Array (ordered list) @items
% Hash (key-value pairs) %config

This guide focuses entirely on the $ sigil - the scalar. A scalar holds exactly one value: a string, a number, a reference, or the special value undef. Perl does not have separate types for integers, floats, and strings the way most languages do. A scalar is whatever you need it to be, and Perl converts between types automatically based on context.

Declaring Variables

You declare a lexical variable with the my keyword:

my $username = "ringo";
my $port     = 8080;
my $pi       = 3.14159;
my $empty;               # declared but undefined (value is undef)

The my keyword limits the variable's visibility to the enclosing block (a pair of curly braces, a file, or an eval). This is called lexical scoping, and it prevents variables from leaking into unrelated code.

use strict and use warnings

Every Perl script you write should start with these two lines:

use strict;
use warnings;

use strict forces you to declare variables before using them. Without it, a typo like $uesrname silently creates a new global variable instead of raising an error.

use warnings alerts you to common mistakes: using an uninitialized variable, treating a string as a number when it does not look like one, or using a deprecated feature. These are not fatal errors - your program still runs - but ignoring them is asking for trouble.

use strict;
use warnings;

my $name = "Perl";
print "Hello, $name!\n";

# Without 'my', strict would stop this cold:
# $oops = 42;  # Global symbol "$oops" requires explicit package name

Always use strict and warnings

Code examples in documentation sometimes omit use strict and use warnings for brevity. Your actual scripts should always include them. The minor inconvenience of declaring variables saves hours of debugging.

Naming Conventions

Variable names follow these rules:

  • Must start with a letter or underscore after the sigil
  • Can contain letters, digits, and underscores
  • Are case-sensitive ($Name and $name are different variables)
  • By convention, use lowercase with underscores: $first_name, $max_retry_count
my $user_name   = "admin";     # good - clear and readable
my $userName    = "admin";     # works but not idiomatic Perl
my $x           = "admin";     # too terse - what does $x mean?
my $2nd_attempt = 1;           # INVALID - can't start with a digit

Strings

Perl has two quoting styles for strings, and the difference matters.

Single-Quoted Strings

Single-quoted strings are literal. Perl takes the characters exactly as written, with two exceptions: \\ produces a single backslash, and \' produces a single quote.

my $path = '/usr/local/bin';
my $msg  = 'The variable $name is not interpolated here';
my $escaped = 'It\'s a backslash: \\';

print $path, "\n";     # /usr/local/bin
print $msg, "\n";      # The variable $name is not interpolated here
print $escaped, "\n";  # It's a backslash: \

Use single quotes when you want the string stored exactly as typed - file paths, regex patterns, or any string that should not have variable substitution.

Double-Quoted Strings

Double-quoted strings interpret escape sequences and interpolate variables:

my $user = "admin";
my $home = "/home/$user";       # /home/admin
my $msg  = "Hello, ${user}!\n"; # Hello, admin! (with newline)

Common escape sequences in double-quoted strings:

Sequence Meaning
\n Newline
\t Tab
\r Carriage return
\\ Literal backslash
\" Literal double quote
\$ Literal dollar sign (prevents interpolation)
\@ Literal at sign (prevents interpolation)
\x{4F60} Unicode character by hex code point
\0 Null byte

The curly braces in ${user} are optional when the variable name is unambiguous, but they help when the variable is followed by text that could be mistaken for part of the name:

my $fruit = "apple";
print "I like ${fruit}s\n";  # I like apples
print "I like $fruits\n";    # Perl looks for $fruits, not $fruit

Heredocs

For multi-line strings, Perl provides heredocs (here-documents). The syntax is <<IDENTIFIER, and the string continues until a line containing only the identifier:

my $html = <<HTML;
<html>
<head><title>$title</title></head>
<body>
  <h1>Welcome</h1>
</body>
</html>
HTML

The identifier can be any word. By convention, it describes the content: HTML, SQL, EOF, END. The rules for quoting apply to the identifier:

Syntax Interpolation Example
<<EOF Yes (like double quotes) <<EOF
<<"EOF" Yes (like double quotes) <<"EOF"
<<'EOF' No (like single quotes) <<'EOF'

Perl 5.26 introduced the indented heredoc with <<~, which strips leading whitespace from each line based on the indentation of the closing identifier:

sub generate_config {
    my $config = <<~CONF;
        server {
            listen 80;
            root /var/www;
        }
        CONF
    return $config;
}

Without the tilde, every line would include the leading spaces. With <<~CONF, Perl removes the common leading whitespace, producing clean output.


Numbers

Perl does not distinguish between integers and floating-point numbers at the language level. A scalar holds a number, and Perl stores it internally as whatever format the operation requires - an integer (IV), an unsigned integer (UV), or a double-precision float (NV).

Integer Literals

You can write integers in several bases:

my $decimal = 255;        # base 10
my $hex     = 0xFF;       # base 16 (hexadecimal)
my $octal   = 0377;       # base 8 (octal, leading zero)
my $binary  = 0b11111111; # base 2 (binary)

All four variables above hold the same value: 255.

Underscores in Numbers

For readability, Perl lets you use underscores as visual separators within numeric literals. The underscores are ignored by the parser:

my $population = 7_900_000_000;  # 7.9 billion
my $hex_color  = 0xFF_AA_00;     # easier to read hex
my $binary     = 0b1111_0000;    # nibble boundaries
my $precise    = 3.141_592_653;  # group decimal places

Floating-Point Numbers

Floating-point numbers use the standard decimal notation or scientific notation:

my $pi      = 3.14159;
my $avogadro = 6.022e23;   # 6.022 * 10^23
my $tiny     = 1.6e-19;    # 1.6 * 10^-19
my $negative = -273.15;

Floating-point precision

Like every language that uses IEEE 754 doubles, Perl has floating-point precision limits. The expression 0.1 + 0.2 == 0.3 evaluates to false because 0.1 + 0.2 is actually 0.30000000000000004. For financial calculations or exact decimal arithmetic, use the Math::BigFloat module from CPAN.

Numeric Operators

Perl provides the standard arithmetic operators:

Operator Meaning Example Result
+ Addition 7 + 3 10
- Subtraction 7 - 3 4
* Multiplication 7 * 3 21
/ Division 7 / 3 2.33333...
% Modulus 7 % 3 1
** Exponentiation 2 ** 10 1024

Perl also has shorthand assignment operators: +=, -=, *=, /=, %=, **=, and the auto-increment/decrement operators ++ and --.

my $count = 10;
$count += 5;    # $count is now 15
$count *= 2;    # $count is now 30
$count++;       # $count is now 31
$count--;       # $count is now 30

Numeric Comparison Operators

Perl has separate comparison operators for numbers and strings. Using the wrong one is a common source of bugs:

Operation Numeric String
Equal == eq
Not equal != ne
Less than < lt
Greater than > gt
Less than or equal <= le
Greater than or equal >= ge
Comparison (spaceship) <=> cmp
# Numeric comparison - compares values as numbers
42 == 42.0;       # true
"42" == "42.0";   # true (both strings become the number 42)

# String comparison - compares character by character
"42" eq "42.0";   # false ("42" and "42.0" are different strings)
"abc" lt "abd";   # true ("c" comes before "d")

The spaceship and cmp operators

<=> and cmp return -1, 0, or 1 depending on whether the left operand is less than, equal to, or greater than the right operand. These are primarily used with sort: sort { $a <=> $b } @numbers sorts numerically, while sort { $a cmp $b } @strings sorts lexically.


Context

Context is one of the most important concepts in Perl. It determines how expressions behave - the same expression can produce different results depending on what surrounds it. There are three primary contexts.

Scalar Context

When Perl expects a single value, it imposes scalar context. Assignment to a scalar variable is the most common trigger:

my @colors = ("red", "green", "blue");
my $count  = @colors;  # scalar context: @colors returns 3 (element count)

An array in scalar context does not return its first element or a stringified version of its contents. It returns the number of elements it contains.

List Context

When Perl expects multiple values, it imposes list context. Assignment to an array or a list of variables triggers it:

my @colors = ("red", "green", "blue");  # list context on the right side
my ($first, $second) = @colors;         # list context: $first="red", $second="green"

Boolean Context

Conditional expressions (if, while, unless, until) impose boolean context. Perl evaluates the expression and determines whether it is true or false according to simple rules covered in the Undef and Truthiness section below.

my @items = (1, 2, 3);
if (@items) {           # boolean context: @items returns 3, which is true
    print "Not empty\n";
}

The scalar() Function

You can force scalar context explicitly with the scalar() function:

my @colors = ("red", "green", "blue");
print "Colors: ", scalar(@colors), "\n";  # Colors: 3

Without scalar(), print imposes list context on @colors, and you would get the elements printed as a concatenated string. With scalar(), you get the count.

How Perl Determines Context

The surrounding code - the operator, function, or assignment target - dictates context. This flowchart summarizes the decision process:

Perl context flowchart showing how expressions are evaluated in scalar context (returns counts), list context (returns elements), or boolean context (truthy/falsy) based on the surrounding code

String Operations

Perl provides a rich set of built-in functions for working with strings. These are some of the most frequently used in day-to-day programming.

length

length returns the number of characters in a string:

my $str = "Hello, World!";
print length($str);  # 13

For byte length on Unicode strings, use bytes::length() or use bytes within a scope. For character semantics (which is almost always what you want), plain length works correctly with use utf8.

substr

substr extracts or replaces a portion of a string:

my $str = "Hello, World!";

# Extract: substr(STRING, OFFSET, LENGTH)
my $word = substr($str, 7, 5);  # "World"

# Negative offset counts from the end
my $last = substr($str, -6);     # "orld!"

# Replace: substr as an lvalue
substr($str, 0, 5) = "Goodbye";  # $str is now "Goodbye, World!"

# Four-argument form: substr(STRING, OFFSET, LENGTH, REPLACEMENT)
my $old = substr($str, 0, 7, "Hello");  # $old = "Goodbye", $str = "Hello, World!"

index and rindex

index finds the first occurrence of a substring and returns its position (0-based). rindex finds the last occurrence. Both return -1 if the substring is not found:

my $path = "/home/admin/docs/admin/notes.txt";
print index($path, "admin");    # 6  (first occurrence)
print rindex($path, "admin");   # 18 (last occurrence)
print index($path, "nobody");   # -1 (not found)

# Optional third argument: starting position
print index($path, "admin", 7); # 18 (start searching from position 7)

chomp and chop

chomp removes the trailing input record separator (usually a newline) from a string and returns the number of characters removed. chop unconditionally removes the last character:

my $line = "data\n";
chomp $line;          # $line is now "data", returns 1
chomp $line;          # $line is still "data", returns 0 (no newline to remove)

my $str = "Hello!";
chop $str;            # $str is now "Hello", returns "!"

Use chomp when processing input (it is safe to call even when there is no trailing newline). Reserve chop for when you specifically need to remove the last character regardless of what it is.

Case Conversion

Perl provides four functions for changing string case:

Function Effect Example
lc Lowercase entire string lc("HELLO") returns "hello"
uc Uppercase entire string uc("hello") returns "HELLO"
lcfirst Lowercase first character lcfirst("HELLO") returns "hELLO"
ucfirst Uppercase first character ucfirst("hello") returns "Hello"
my $name = "perl";
print ucfirst($name);   # Perl
print uc($name);         # PERL

Concatenation and Repetition

The dot operator (.) concatenates strings. The x operator repeats a string:

my $greeting = "Hello" . ", " . "World!";   # "Hello, World!"
my $line     = "-" x 40;                     # forty dashes
my $padding  = " " x 8;                      # eight spaces

# .= appends to an existing string
my $log = "Step 1 done";
$log .= "; Step 2 done";  # "Step 1 done; Step 2 done"

Undef and Truthiness

What undef Is

undef is Perl's representation of "no value." A variable that has been declared but not assigned holds undef:

my $x;               # $x is undef
my $y = undef;       # explicit, same result

Using undef in a numeric context produces 0. In a string context, it produces the empty string "". In both cases, if use warnings is active, Perl emits a "Use of uninitialized value" warning.

use warnings;
my $x;
print $x + 1;   # prints 1, but warns: Use of uninitialized value $x in addition
print $x . "!"; # prints !, but warns: Use of uninitialized value $x in concatenation

Perl's Truth Rules

Perl does not have a dedicated boolean type. Any scalar value is either true or false. The rules are simple - exactly four values are false:

Value Why it is false
undef No value
"" (empty string) Empty
0 (the number zero) Zero
"0" (the string containing only a zero) Stringifies to zero

Everything else is true. This includes "0.0", " " (a space), "00" (two zeros), negative numbers, and strings like "false". If this seems surprising, remember: only the four values in the table above are false.

# All of these are TRUE in Perl:
if ("false")   { print "The string 'false' is true!\n"; }
if ("0.0")     { print "The string '0.0' is true!\n"; }
if (-1)        { print "Negative numbers are true!\n"; }
if ("00")      { print "The string '00' is true!\n"; }
if (" ")       { print "A space is true!\n"; }

# All of these are FALSE:
if (0)     { }   # false - the number zero
if ("")    { }   # false - empty string
if (undef) { }   # false - no value
if ("0")   { }   # false - the string "0"

The 'not' operator

The ! operator (or the not keyword) inverts a boolean value. !0 is 1 (true), and !1 is "" (false, the empty string). The !! double-negation trick forces any value into 1 or "".

The defined() Function

The defined() function tells you whether a scalar has a value (even if that value is false):

my $zero  = 0;
my $empty = "";
my $nope;         # undef

print defined($zero)  ? "yes" : "no";  # yes - 0 is defined
print defined($empty) ? "yes" : "no";  # yes - "" is defined
print defined($nope)  ? "yes" : "no";  # no  - undef is not defined

Use defined() when you need to distinguish between "a value was provided" and "no value exists." A function returning 0 is different from a function returning undef - the first succeeded with a zero result, the second might indicate an error.

The Defined-Or Operator

Perl 5.10 introduced the defined-or operator //, which returns the left operand if it is defined, otherwise the right operand:

my $port = $config_port // 8080;  # use $config_port if defined, otherwise 8080

This is different from || (logical or), which tests for truth rather than definedness. If $config_port is 0 (a valid port? no, but a valid number), || would skip it because 0 is false, while // would keep it because 0 is defined:

my $val = 0;
my $a = $val || 42;   # $a = 42 (0 is false, so || uses the right side)
my $b = $val // 42;   # $b = 0  (0 is defined, so // uses the left side)

Numeric Functions

Perl provides built-in functions for common mathematical operations. For anything beyond the basics, the POSIX module and CPAN modules like Math::Trig extend the repertoire.

abs, int, sqrt

print abs(-42);       # 42 - absolute value
print int(3.999);     # 3  - truncates toward zero (does NOT round)
print int(-3.999);    # -3 - truncates toward zero
print sqrt(144);      # 12 - square root

abs returns the absolute value. int truncates toward zero - it drops the decimal part without rounding. sqrt returns the square root.

int() does not round

A common mistake is using int() to round numbers. int(2.9) returns 2, not 3. To round to the nearest integer, use int($n + 0.5) for positive numbers, or sprintf("%.0f", $n) for general rounding, or the POSIX::round function.

rand and srand

rand returns a random floating-point number from 0 (inclusive) to the given value (exclusive). Without an argument, it returns a value between 0 and 1:

my $roll  = int(rand(6)) + 1;    # random integer 1-6 (simulates a die)
my $coin  = rand() < 0.5 ? "heads" : "tails";
my $pct   = rand(100);           # 0 <= $pct < 100

srand seeds the random number generator. Perl calls it automatically the first time you use rand, but you can call it explicitly for reproducible sequences:

srand(42);          # set seed for reproducible results
print rand(), "\n"; # same value every time with seed 42

Not cryptographically secure

rand() uses a predictable pseudo-random number generator. For security-sensitive work (tokens, passwords, nonces), use Crypt::URandom or read from /dev/urandom directly.

hex and oct

hex converts a hexadecimal string to a number. oct converts a string with a base prefix (0x, 0b, 0) to a number:

print hex("ff");     # 255
print hex("0xFF");   # 255

print oct("377");    # 255 (octal)
print oct("0xFF");   # 255 (hex, via prefix)
print oct("0b11111111"); # 255 (binary, via prefix)

Despite its name, oct handles hex and binary prefixes too. It examines the prefix to determine the base.

sprintf for Formatting

sprintf returns a formatted string without printing it (unlike printf, which prints directly). The format codes follow C conventions:

# Decimal places
my $pi = sprintf("%.4f", 3.14159265);   # "3.1416"

# Padding
my $padded = sprintf("%010d", 42);       # "0000000042"

# Multiple values
my $msg = sprintf("%-20s %5d %8.2f", "Widget", 100, 29.95);
# "Widget                 100    29.95"

# Hex and octal output
my $hex = sprintf("%x", 255);    # "ff"
my $oct = sprintf("%o", 255);    # "377"
my $bin = sprintf("%b", 255);    # "11111111"

Common format specifiers:

Code Type Example
%d Decimal integer sprintf("%d", 42) returns "42"
%f Floating point sprintf("%.2f", 3.14) returns "3.14"
%e Scientific notation sprintf("%e", 12345) returns "1.234500e+04"
%s String sprintf("%-10s", "hi") returns "hi "
%x Hexadecimal (lowercase) sprintf("%x", 255) returns "ff"
%o Octal sprintf("%o", 8) returns "10"
%b Binary sprintf("%b", 10) returns "1010"

Special Variables

Perl has dozens of built-in special variables that control the interpreter's behavior and provide access to runtime information. These are documented in perlvar. Here are the ones you will encounter most often when working with scalars.

$_ - The Default Variable

$_ is the default variable. Many Perl functions and constructs operate on $_ when you do not specify an explicit argument:

# Without $_
for my $item (@list) {
    print $item, "\n";
}

# With $_ (implicit)
for (@list) {
    print $_, "\n";     # $_ holds each element in turn
}

# Even shorter - print defaults to $_
for (@list) {
    print;              # prints $_ followed by nothing (no newline!)
}

Functions that default to $_ include chomp, chop, print, length, lc, uc, defined, ref, and many others. Loop constructs like for, foreach, while (<FILEHANDLE>), and map/grep set $_ implicitly.

# $_ in a while-read loop
while (<STDIN>) {
    chomp;              # chomp($_)
    next if /^#/;       # skip comments - regex matches against $_
    print length, "\n"; # length($_)
}

When to use $_ and when not to

$_ makes short code concise, especially one-liners and map/grep blocks. In longer code, named variables are clearer. If you find yourself wondering "what does $_ refer to here?", use an explicit variable instead.

$! - System Error

$! holds the current system error message (from the C errno variable). Check it immediately after a system call fails:

open(my $fh, '<', '/etc/shadow') or die "Cannot open: $!\n";
# If the open fails: "Cannot open: Permission denied"

In numeric context, $! gives the errno number. In string context, it gives the human-readable message.

$@ - Eval Error

$@ holds the error message from the most recent eval block or die:

eval {
    die "Something went wrong";
};
if ($@) {
    print "Caught error: $@\n";
    # "Caught error: Something went wrong at script.pl line 2."
}

$/ - Input Record Separator

$/ defines what Perl considers a "line" when reading from a filehandle. It defaults to "\n". Change it to read records delimited by something other than newlines, or set it to undef to read an entire file at once:

# Slurp entire file into a single string
my $contents;
{
    local $/;   # temporarily undefine $/ within this block
    open(my $fh, '<', 'data.txt') or die "Cannot open: $!\n";
    $contents = <$fh>;
    close $fh;
}

The local keyword temporarily overrides $/ for the duration of the block. When the block exits, $/ reverts to its previous value. This is the idiomatic way to modify global special variables safely.

$\ - Output Record Separator

$\ is appended to every print output. It defaults to undef (nothing appended). Set it to "\n" if you want every print to automatically add a newline:

{
    local $\ = "\n";
    print "First line";    # prints "First line\n"
    print "Second line";   # prints "Second line\n"
}

$. - Line Number

$. tracks the current line number of the most recently read filehandle:

open(my $fh, '<', 'config.txt') or die "Cannot open: $!\n";
while (<$fh>) {
    print "$.: $_" if /error/i;  # prints line number and matching line
}
close $fh;

$. resets when you explicitly close a filehandle, but not when you open a new one without closing the old. Use close explicitly to keep $. predictable.


Further Reading


Previous: Introduction: Why Perl, and Why Unix First | Next: Arrays, Hashes, and Lists | Back to Index

Comments