Home / Alt manpages / perlfaq7(1)

  • perlfaq7(1)
  • User command
  • linux

Write Safer Perl by Taming Precedence, Context and References

You will leave with a small set of Perl habits that prevent some of the most expensive language-level mistakes: confusing list-operator precedence, losing track of scalar versus list context, and reaching for symbolic references when a real data structure is needed. The examples match Perl 5.38.2 and perlfaq7 version 5.20210520, supplied here by the perl-doc package.

Allow about 20 minutes. You need a shell and the perl interpreter. Everything in this guide runs as an ordinary user and only prints values or checks syntax. No elevated privileges, service restart or file deletion is required.

1. Check the interpreter before debugging the code

First establish which Perl you are actually running. A different interpreter in a test shell and a script's shebang can make a language question look like a deployment problem.

$ command -v perl
/usr/bin/perl
$ perl -e 'print "$^V\n"'
v5.38.2

The FAQ is installed with this machine's Perl documentation, but its answers describe several historical alternatives. Prefer the modern forms below on Perl 5.38.2. If a script says bad interpreter, run it explicitly with perl script.pl first. A wrong shebang path, CRLF line endings or a missing execute bit can all be involved; none is a Perl syntax error.

Checkpoint

The version printed by perl -e is the interpreter whose behaviour you are testing.

2. Make context and return values visible

Perl functions can return a different useful shape in scalar and list context. When you need only selected fields, take a slice rather than assigning a long list and hoping its positions remain clear. This example selects device, inode, user ID and group ID from stat:

use strict;
use warnings;

my ($device, $inode, $uid, $gid) = (stat "/etc/passwd")[0, 1, 4, 5];
print "uid=$uid gid=$gid\n";

Parentheses around the stat call make the slice boundary obvious. The file is read for metadata only. If you do not need a field in the middle of an ordinary list assignment, use undef as a deliberate placeholder, for example my ($device, $inode, undef, undef, $uid, $gid) = stat "/etc/passwd";.

Do not confuse a filehandle operation with a type marker. <FILE> reads a record from the handle named FILE; it is not a file type and it is not part of calls such as eof(FILE) or seek(FILE, 0, 2).

3. Parenthesise list operators at the point of failure

Perl's C-like operators retain their familiar precedence, but list operators such as print, exec and unlink can make an expression read differently from the way it is grouped. The robust pattern is to use parentheses or the deliberately low-precedence English operator when checking a result.

use strict;
use warnings;

open my $input, '<', '/path/to/input.txt'
    or die "open failed: $!\n";
print while <$input>;
close $input or die "close failed: $!\n";

For an operation that must succeed, write (operation) or die ..., not an expression whose arguments can absorb the || unexpectedly. The FAQ's representative example is unlink $file || die "snafu"; the parser treats that as unlink ($file || die "snafu"). If deletion is genuinely intended, unlink $file or die "snafu" expresses the check more clearly. Treat that as a destructive command and inspect the target before using it.

Exponentiation has a separate trap: it binds more tightly than unary minus and associates from the right.

$ perl -e 'print((-2**2), " ", (2**3**2), "\n")'
-4 512

Use parentheses when the mathematical intention matters. The output is a quick verification that you are testing the same precedence rules described by the installed FAQ.

4. Use real references for changing data

A variable containing a variable's name is usually a sign that the program needs a hash. Symbolic references only address package globals, are forbidden by use strict 'refs', and can accidentally turn input into access to the symbol table. Store user-selected values in data you own instead.

use strict;
use warnings;

my %user_vars = (fred => 23, barney => 7);
my $name = 'fred';
$user_vars{$name}++;
print "$name=$user_vars{$name}\n";

Save that short program as /tmp/user-vars.pl if you want to reproduce the displayed command:

$ perl /tmp/user-vars.pl
fred=24

For related records, use a multilevel hash rather than separate hashes named after people:

my %folks;
$folks{fred}{wife} = 'wilma';
$folks{barney}{wife} = 'betty';

Pass arrays, hashes, filehandles, regular expressions and code by reference or as a scalar value. A function can accept \@items, \%options, qr/failed/i or an anonymous subroutine without exposing the package symbol table.

5. Choose a closure or state for persistent values

A closure is an anonymous subroutine that retains access to a lexical variable from the scope where it was created. That makes it useful for a small private counter or a function configured once.

use strict;
use warnings;

sub make_adder {
    my ($amount) = @_;
    return sub { $_[0] + $amount };
}

my $add_twenty = make_adder(20);
print $add_twenty->(5), "\n";

Save the adder example as /tmp/add.pl before running:

$ perl /tmp/add.pl
25

On Perl 5.10 and newer, state is the shorter choice when persistence belongs to one named subroutine:

use strict;
use warnings;
use feature 'state';

sub next_id {
    state $id = 1;
    return $id++;
}

print next_id(), " ", next_id(), "\n";

Do not use a package global merely because a value must survive a call. A closure or state documents who owns the value and limits accidental access.

6. Keep warnings local when an exception is understood

Warnings should normally stay enabled. If a narrow operation is expected to involve an undefined value, suppress only the relevant category in a small block and leave the rest of the program checked.

use strict;
use warnings;

my ($left, $right);
my $total;
{
    no warnings 'uninitialized';
    $total = $left + $right;
}
print "$total\n";

The block limits the exception's scope. Avoid the older global $^W switch in new code unless you are maintaining code that specifically requires it.

Done means

  • You verified the interpreter version with perl -e.
  • You make list slices, context and filehandle syntax explicit.
  • You parenthesise list-operator checks and test exponentiation with intentional grouping.
  • You use hashes and hard references instead of symbolic references for application data.
  • You use closures or state for private values and keep warning exceptions local.