Build Fixed-Width Perl Reports with format and write
You will finish with a small Perl report that prints aligned text, numbers and a wrapped description, using Perl's built-in format and write features. The examples match Perl v5.38.2 and the perl-doc package installed here as version 5.38.2-3.2ubuntu0.6.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes. You need Perl and a text editor. Check the version with perl -v. This guide only writes a file in the directory you choose and prints to standard output; it does not need elevated privileges or change system configuration.
1. Create a minimal report
Start with one format named STDOUT, which is also the default format for the standard output filehandle. A format is declared between a line beginning with format and a line containing a single dot in column 1. Save this as /tmp/perl-report.pl:
#!/usr/bin/perl
use strict;
use warnings;
my ($name, $team, $total) = ('Ada Lovelace', 'Research', 42);
format STDOUT =
Name: @<<<<<<<<<<<<<<<<<<<< Team: @<<<<<<<<<< Total: @###
$name, $team, $total
.
write;
The picture line contains literal labels and fields. Each @ starts a regular field. Repeated < characters make a left-justified text field, while repeated # characters make a right-justified numeric field. The following line supplies values in the same order as the fields.
Run it as an ordinary user:
$ perl /tmp/perl-report.pl
Name: Ada Lovelace Team: Research Total: 42
Checkpoint: if the values are shifted into the wrong columns, count the fields from left to right and compare them with the comma-separated expressions. Picture lines do not interpolate variables themselves.
2. Add numeric precision and overflow checks
Use a dot inside a numeric field to reserve a decimal point. A leading zero in place of the first hash requests zero padding. If a value cannot fit, Perl prints hashes as visible overflow evidence rather than silently shortening the number.
format STDOUT =
Count: @### Rate: @.### Code: @0### Limit: @###
42, 3.1415, 7, 10000
.
write;
Expected output is:
Count: 42 Rate: 3.142 Code: 0007 Limit: ####
The field width is a limit, not a suggestion. Widen @### when a legitimate value can exceed three positions. An undefined value in a special caret field is blank, but an ordinary numeric field is not a substitute for input validation.
3. Wrap a long value without losing the remainder
For descriptions, a caret field consumes part of one scalar each time it is used. Perl removes the consumed text from that scalar during the write call. Put several caret fields in a vertical stack, and add ~~ to repeat a line until the field is exhausted:
my $description = 'Perl formats are useful for small reports when columns and page headers matter.';
format STDOUT =
Description: ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$description
~~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$description
.
write;
The output is split across lines at suitable break characters. The original scalar is deliberately altered, so do not expect to reuse $description after write unless you copied it first. A caret field is a formatting operation with state, not a read-only display expression.
To suppress a line whose caret fields have no text left, put one tilde on that line. It is rendered as a space. A pair of tildes means repeat the line, so check that at least one field will eventually become empty. A regular field fed by a value that never changes can create an endless report.
4. Choose between @* and ^*
Use @* when a scalar already contains multiple lines and you want all of them emitted without truncation. The final newline is removed, but other characters are emitted verbatim. Use ^* when you want one line of a scalar at a time and need the variable-width field behaviour:
my $text = "line 1\nline 2\nline 3";
format STDOUT =
Text: ^*
$text
~~ ^*
$text
.
write;
Expected output:
Text: line 1
line 2
line 3
Use ^* for a report field that should advance through input one line at a time. Use @* for a complete multi-line value. These fields are not interchangeable, and the caret form still consumes its scalar.
5. Add a page header only when you need pages
For a filehandle named STDOUT, Perl looks for a top-of-page format named STDOUT_TOP. It uses that format when a page starts. A header format can contain literal text and fields just like the body format:
format STDOUT_TOP =
Inventory report
Item Owner Count
-------------------- ----------------- -----
.
format STDOUT =
@<<<<<<<<<<<<<<<<<<<< @<<<<<<<<<<<<<<<<< @###
$item, $owner, $count
.
write;
Page behaviour depends on the selected filehandle's format variables, including the page length. Do not mix print and write casually: if you do, you are responsible for tracking the lines left before the next header. There is no automatic footer format. A fixed footer must be managed by your program using the format line-count variables.
6. Keep filehandle settings local and testable
The current format name, top format name, page number and lines per page are stored per filehandle. If you need a named format on another handle, select that handle, set its format names, then restore the previous handle:
my $old = select(REPORT);
$~ = 'Report';
$^ = 'Report_TOP';
select($old);
write(REPORT);
Use a temporary variable for the previous handle. The longer one-expression select idiom exists, but it is harder to inspect when debugging. Verify a named format by running the complete program and checking both headings and data columns. There is no undo command for output already written, so redirect tests to a temporary file if the report feeds another system.
Done means
perl -vreports the version you tested.- Your format has one value expression for each picture field, in order.
- Text widths are large enough for real input, and numeric overflow is visible.
- You know whether wrapped fields consume their source scalar.
- A final run produces the expected headings, alignment and line breaks without elevated privileges.