Home / Alt manpages / perllol(1)

  • perllol(1)
  • User command
  • linux

Build and Inspect Perl Arrays of Arrays Safely

You will finish with a small Perl program that builds rows from input, reads individual cells, appends values, prints the structure and takes a two-dimensional slice. The examples follow perllol(1) as installed with Perl 5.38.2 and the perl-doc package version 5.38.2-3.2ubuntu0.6.

Allow about fifteen minutes. You need Perl 5.38 or newer and a shell. The examples only read standard input and create in-memory data. They do not write files, change system configuration or need elevated privileges.

1. Start with the two different forms

An array of arrays is an ordinary Perl array whose elements are array references. Use parentheses when assigning the outer array:

use v5.10;
use strict;
use warnings;

my @rows = (
    [ 'red',   'green', 'blue' ],
    [ 'small', 'medium', 'large' ],
);

say $rows[1][2];

Expected output:

large

The expression $rows[1][2] means row 1, column 2. Both indexes start at zero. Each outer element is a reference, but adjacent square brackets let Perl follow that reference for you.

A scalar containing the whole structure uses square brackets for the outer constructor:

my $table = [
    [ 'red', 'green', 'blue' ],
    [ 'small', 'medium', 'large' ],
];

say $table->[1][2];

The first arrow is required because $table is a scalar holding a reference. Writing $table[1][2] would mean a package variable named $table, not the scalar reference.

Checkpoint

Parentheses make a list assigned to @rows; square brackets make an array reference assigned to $table. Do not swap those outer delimiters casually.

2. Add rows from standard input

For line-oriented data, split each input line and wrap the resulting list in square brackets before pushing it. This keeps the row as one array reference:

use strict;
use warnings;

my @rows;
while (<>) {
    push @rows, [ split ];
}

print "rows: ", scalar @rows, "\n";
print "first cell: $rows[0][0]\n" if @rows && @{$rows[0]};

Save that as rows.pl and run it with ordinary input:

$ printf '%s\n' 'alpha beta' 'gamma delta epsilon' | perl rows.pl
rows: 2
first cell: alpha

The diamond operator reads files named on the command line, or standard input when no file is supplied. The if guard avoids dereferencing an absent first row.

This is a common mistake:

$rows[0] = @values;

It assigns the number of elements in @values, because the scalar on the left puts the right side in scalar context. Use $rows[0] = [ @values ] when you need a new row reference.

3. Update cells and append to a row

Direct assignment can create missing positions. Perl fills skipped array elements with undef, so use this deliberately:

$rows[3][2] = 'created later';
print defined $rows[3][0] ? $rows[3][0] : 'undef', "\n";

The assignment creates row 3 and column 2. The earlier row positions are present as undefined values, not meaningful empty strings. If a sparse structure is not what you want, validate row and column indexes before assigning.

To append values to an existing row, dereference that row explicitly. Perl 5.24 and later support the postfix dereference used here:

push $rows[0]->@*, 'extra-one', 'extra-two';
print join('|', $rows[0]->@*), "\n";

Expected output for the earlier input is:

alpha|beta|extra-one|extra-two

If the row may not exist, check it before pushing. A missing row is an undefined scalar, not an array reference you can safely dereference.

4. Print every row without leaking references

print @rows does not recursively print the cells. It prints reference values in a form such as ARRAY(0x...). Walk the outer array and dereference each row:

for my $row_index (0 .. $#rows) {
    my $row = $rows[$row_index];
    next unless defined $row;
    print "row $row_index: ", join(', ', @$row), "\n";
}

For a cell-by-cell report, add an inner loop:

for my $i (0 .. $#rows) {
    next unless defined $rows[$i];
    for my $j (0 .. $#{$rows[$i]}) {
        next unless defined $rows[$i][$j];
        print "cell $i,$j = $rows[$i][$j]\n";
    }
}

The next checks matter for sparse data. A range based on an undefined row is not a useful way to inspect it. For a quick diagnostic of a complicated structure, the standard Data::Dumper module can emit Perl-readable output:

use Data::Dumper;
print Dumper \@rows;

5. Take a row or rectangular slice

A row slice returns selected cells from one row. The postfix array slice syntax is concise on this Perl version:

my @middle = $rows[0]->@[1 .. 2];
print join(', ', @middle), "\n";

To copy columns 1 through 2 from rows 0 through 1, build a fresh outer array and a fresh inner reference for each row:

my @rectangle;
for my $i (0 .. 1) {
    push @rectangle, [ $rows[$i]->@[1 .. 2] ];
}

for my $row (@rectangle) {
    print join(', ', @$row), "\n";
}

With the two-row example, the copied output is:

beta, green
delta, epsilon

The slice creates new row references, so later changes to @rectangle do not change those row arrays in @rows. The cell values themselves are copied as scalar values. For nested references, use a deliberate deep-copy strategy instead of assuming a shallow slice duplicates the entire object graph.

6. Verify the finished example

Run Perl's syntax check before executing a larger script:

$ perl -c rows.pl
rows.pl syntax OK

Then test with an empty input stream as well as normal rows. The guarded first-cell report should produce only:

$ : | perl rows.pl
rows: 0

No rollback is needed for these examples because they only build temporary in-memory structures. If your own script has already modified a sparse row, undo it by assigning a deliberate row reference or by rebuilding the outer array from the original input; do not assume that assigning undef removes a position.

Done means

  • You can distinguish an outer array from a scalar containing an array reference.
  • Input rows are wrapped in [ ... ], rather than accidentally reduced to their element count.
  • You account for zero-based indexes, undefined sparse positions and missing rows.
  • You print dereferenced cells or rows instead of raw reference addresses.
  • You can copy a row slice or rectangular slice without confusing it with a deep copy.
  • perl -c passes before the script is used on real input.