The Comprehensive Reference
The Perl Programming Guide
GUI Development with Perl/Tk on FreeBSD 16 via Lima
Perl 5.36+
FreeBSD 16 CURRENT
Lima ≥ 2.1
Perl/Tk · X11
Setup: Run Perl/Tk programs inside your Lima FreeBSD 16 VM with X11 forwarding through XQuartz. See §21 Lima Setup for the complete walkthrough. Keyboard shortcuts use Ctrl (not Cmd) on FreeBSD/X11.
Part I
The Perl Language
§01
Introduction to Perl
Perl was created by Larry Wall in 1987. On FreeBSD it is a first-class citizen, available via pkg install perl5 and deeply integrated into the ports tree with over 25,000 modules available as pre-built p5-* packages. It excels at text processing, system scripting, and GUI development via Perl/Tk.
Perl's motto: "There's More Than One Way To Do It"
| Feature | Description |
|---|---|
| Interpreted | Runs via /usr/local/bin/perl on FreeBSD |
| Dynamically typed | No type declarations needed |
| CPAN | Many pre-built as pkg install p5-* on FreeBSD |
| Tk GUI | pkg install p5-Tk — mature X11 toolkit |
§02
Running Perl Programs
Shebang on FreeBSD
perl
#!/usr/local/bin/perl # FreeBSD: pkg Perl lives here
#!/usr/bin/env perl # Portable: searches $PATHFreeBSD path:
pkg install perl5 puts Perl at /usr/local/bin/perl, not /usr/bin/perl.bash
perl myscript.pl # Run
perl -c myscript.pl # Syntax check only
perl -w myscript.pl # Warnings
chmod +x s.pl && ./s.pl # Run via shebang§03
Scalars
perl
my $name = "Alice"; my $age = 30; my $pi = 3.14159;
my $city = "Sydney";
print "Hello $city!\n"; # Interpolates
print 'Literal $city\n', "\n"; # Single-quotes: no interpolation
print defined(undef) ? "defined" : "undef";§04
Strings & String Operators
perl
my $s = "Hello" . ", " . "World!"; # . = concatenation
my $l = "-" x 40; # x = repetition
length("hello") # 5
uc("hi") # "HI"
ucfirst("perl") # "Perl"
substr("Submarine", 3, 4) # "marin"
index("Submarine", "arin") # 4
chomp($s); # Remove trailing newline
# sprintf
sprintf("%08d", 42) # "00000042"
sprintf("%.4f", 3.14159) # "3.1416"
sprintf("%-10s|","hi") # "hi |"
# String comparison: eq ne lt gt le ge cmp§05
Numbers & Numeric Operators
perl
use POSIX qw(floor ceil);
my $hex=0xFF; my $bin=0b1010; my $big=1_000_000;
# + - * / % ** ++ --
# == != < > <= >= <=> (spaceship)
abs(-5) # 5
int(3.9) # 3
sqrt(144) # 12
floor(3.7) # 3 ceil(3.2) # 4
my @s = sort { $a <=> $b } (5,2,9,1);§06
Arrays
perl
use List::Util qw(sum min max first any all);
my @c = ("red","green","blue");
my @r = (1..10);
my @w = qw(banana apple cherry);
$c[0] # "red" $c[-1] # "blue"
scalar @c # 3 $#c # 2
push @c,"yellow"; my $v=pop @c;
unshift @c,"white"; my $f=shift @c;
my @ev = grep { $_ % 2 == 0 } @r;
my @d2 = map { $_ * 2 } @r;
my @as = sort { $a <=> $b } @r;
sum(@r) # 55 min(@r) # 1 max(@r) # 10
join(", ",@c) # "red, green, blue"
split(/,/,"a,b,c") # ("a","b","c")§07
Hashes
perl
my %p = (name=>"Bob", age=>25, city=>"Austin");
$p{email} = "b@x.com";
delete $p{city};
exists $p{name}; # Boolean
foreach my $k (sort keys %p) { printf "%s: %s\n",$k,$p{$k} }
# Frequency count
my %freq; $freq{$_}++ for @data;
# Unique
my %seen; my @u = grep { !$seen{$_}++ } @data;§08
Control Flow
perl
if ($n > 90) { ... }
elsif ($n > 80) { ... }
else { ... }
unless ($err) { ... } # Opposite of if
print "ok\n" if $flag; # Postfix
my $r = ($n>0) ? "pos" : "neg"; # Ternary
my $v = $input // "default"; # Defined-or
open(my $fh,"<",$f) or die "$!";§09
Loops
perl
while ($i < 10) { $i++ }
foreach my $item (@arr) { print "$item\n" }
print "$_ " for 1..10; # Postfix
next; # Skip last; # Break
OUTER: for my $i (1..3) {
for my $j (1..3) { next OUTER if $j==2 }
}
my @sq = map { $_ ** 2 } 1..8;
my @od = grep { $_ % 2 } 1..10;§10
Regular Expressions
perl
if ($s =~ /pat/i) { } # Match
if ($s !~ /pat/) { } # Negated
$s =~ s/old/new/g; # Substitute global
$s =~ tr/a-z/A-Z/; # Transliterate
# Capture groups
if ($d =~ /(\d{4})-(\d{2})-(\d{2})/) {
my ($y,$m,$d2) = ($1,$2,$3);
}
# Named: $+{year} Global: ($s=~/(\d+)/g)
# Extended /x — add comments in the regex§11
Subroutines
perl
sub greet { my ($n,$g)=@_; $g//="Hello"; "$g, $n!" }
# Closure factory
sub make_mult { my($f)=@_; sub { $_[0]*$f } }
my $dbl = make_mult(2);
print $dbl->(7); # 14
# Dispatch table
my %ops = (add=>sub{$_[0]+$_[1]}, mul=>sub{$_[0]*$_[1]});
$ops{add}->(10,3); # 13
# Recursive
sub fact { $_[0]<=1 ? 1 : $_[0]*fact($_[0]-1) }§12
References & Data Structures
perl
my $ar = [1,2,3]; # Anon array ref
my $hr = {a=>1}; # Anon hash ref
$ar->[0] $hr->{a}
my @team = (
{name=>"Alice",role=>"dev",level=>5},
{name=>"Bob", role=>"ops",level=>3},
);
my @sorted = sort { $b->{level} <=> $a->{level} } @team;
my @devs = grep { $_->{role} eq "dev" } @team;
use Data::Dumper; print Dumper(\@team);§13
File I/O
perl
open(my $fh,"<",$f) or die "$!"; # Read
open(my $fh,">",$f) or die "$!"; # Write
open(my $fh,">>",$f) or die "$!"; # Append
while (my $line=<$fh>) { chomp $line; ... }
my $all = do { local $/; <$fh> }; # Slurp
# -e exists -f file -d dir -r readable -s size§14
Modules & Packages
FreeBSD tip: Most common CPAN modules have
p5-* packages in the ports tree. pkg install p5-Module-Name is faster than building from source.| Module | FreeBSD Package | Key Exports |
|---|---|---|
List::Util | perl5 core | sum min max first any all reduce |
POSIX | perl5 core | floor ceil |
Data::Dumper | perl5 core | Dumper |
Tk | p5-Tk | GUI widgets and MainLoop |
§15
Error Handling
perl
die "Fatal\n"; warn "Warning\n";
eval { die "oops\n" };
print "Caught: $@" if $@;
eval { die {code=>404,msg=>"Not found"} };
if (ref $@ eq 'HASH') { printf "%d: %s\n",$@->{code},$@->{msg} }§16
Object-Oriented Perl
perl
package Animal;
sub new { my($cl,%a)=@_; bless {name=>$a{name}//"?",sound=>$a{sound}//"..."},$cl }
sub speak { printf "%s says: %s\n",$_[0]->{name},$_[0]->{sound} }
package Dog;
use parent 'Animal';
sub new { my($cl,%a)=@_; $a{sound}="Woof"; $cl->SUPER::new(%a) }
sub fetch { print "$_[0]->{name} fetches!\n" }
package main;
my $d = Dog->new(name=>"Rex");
$d->speak; $d->fetch;
$d->isa("Animal"); # 1 (true)§17
Useful Built-ins
perl
use List::Util qw(sum min max first any all reduce);
sum(1..10) # 55
reduce { $a*$b } 1..5 # 120
# Schwartzian Transform
my @bl = map { $_->[0] }
sort { $a->[1] <=> $b->[1] }
map { [$_,length($_)] }
@words;§18
Command-Line Arguments
perl
use Getopt::Long;
GetOptions(
"verbose|v" => \$verbose,
"output|o=s" => \$output,
"count|n=i" => \$count,
) or die "Usage: $0 [--verbose] [--output FILE]\n";§19
Perl Best Practices
- ✓ Always
use strict; use warnings; - ✓ Declare every variable with
my - ✓ Three-argument
openwithor die - ✓
chompall input lines - → Use
#!/usr/local/bin/perlon FreeBSD - → Prefer
pkg install p5-*over cpanm when available - → Use
<Control-key>bindings for Tk shortcuts on FreeBSD/X11 - ✗ Never use
sleep()inside Tk callbacks — useafter() - ✗ Never mix
pack/grid/placein the same container
Part II
GUI Development on FreeBSD 16 via Lima
§20
GUI Overview & Toolkit Choices on FreeBSD
| Toolkit | pkg Package | Notes |
|---|---|---|
| Perl/Tk | p5-Tk | X11-native, best documentation, best for learning |
| Tkx | p5-Tkx | Modern Tk binding |
| wxPerl | p5-Wx | Native widgets via wxGTK |
| Gtk3 | p5-Gtk3 | GTK3, natural on X11 |
Architecture: Lima + XQuartz + X11
text
macOS Host Lima VM (FreeBSD 16)
──────────────────────────────────────────────────────
XQuartz.app ←── X11 protocol ─── perl gui_04_app.pl
(Display :0) (over SSH -X) Perl/Tk → X11 calls§21
Setting Up FreeBSD 16 in Lima
1
Install Lima, QEMU, and XQuartz on your macOS host
bash
brew install lima qemu
brew install --cask xquartz
# Log out and back in after XQuartz installs
limactl --version # Must be 2.1.0 or newer2
Create and start the FreeBSD 16 Lima VM
bash
limactl start template:experimental/freebsd-16
# Choose: Proceed with current configuration
# First boot downloads ~1 GB — takes several minutesLima caveat: FreeBSD 16 in Lima is experimental. No automatic port forwarding (use
ssh -L) and no host directory mounting (use scp or rsync).3
Connect with X11 forwarding for GUI apps
bash
PORT=$(limactl list --format '{{.SSHLocalPort}}' experimental/freebsd-16)
ssh -X -p $PORT -i ~/.lima/_config/user \
-o StrictHostKeyChecking=no \
-o ForwardX11Trusted=yes \
127.0.0.1
# Handy alias — add to ~/.zshrc:
alias fbsd='ssh -X -p $(limactl list --format "{{.SSHLocalPort}}" experimental/freebsd-16) \
-i ~/.lima/_config/user -o StrictHostKeyChecking=no 127.0.0.1'4
Install Perl and Perl/Tk inside the VM
bash
# Inside the Lima VM (connected with SSH -X):
pkg update
pkg install perl5 p5-Tk xorg-fonts-truetype dejavu-ttc
perl -MTk -e 'print "Tk $Tk::VERSION OK\n"'
echo $DISPLAY # Should show: localhost:10.05
Copy scripts into the VM and run them
bash
# On macOS host:
scp -P $PORT -i ~/.lima/_config/user *.pl 127.0.0.1:~/
# Inside the VM:
perl perl_tutorial.pl # Terminal only
perl perl_gui_01_basics.pl # Opens a window via XQuartz⚡ VM Quick Reference
bash
limactl start experimental/freebsd-16 # Start
limactl stop experimental/freebsd-16 # Stop
limactl shell experimental/freebsd-16 # Shell (no X11)
limactl list # Status
limactl delete experimental/freebsd-16 # Remove§22
Perl/Tk Core Concepts
perl
#!/usr/local/bin/perl
use strict; use warnings; use Tk;
my $mw = MainWindow->new;
$mw->title("My App");
$mw->geometry("400x300");
$mw->Label(-text => "Hello, FreeBSD!")->pack;
MainLoop; # Event loop — never returns-textvariable — Live Binding
perl
my $msg = "Hello";
$mw->Label(-textvariable => \$msg)->pack;
$msg = "Updated!"; # Label shows "Updated!" immediately§23
Widgets — The Building Blocks
Label
Text or image display
Button
Clickable, -command callback
Entry
Single-line text input
Text
Multi-line, styled tags
Frame
Invisible container
LabelFrame
Bordered + title
Checkbutton
Independent toggle
Radiobutton
Exclusive choice group
Scale
Numeric range slider
Listbox
Scrollable item list
Canvas
Drawing surface
Menu
Menubar + context menus
§24
Layout Managers
Critical: Never mix
pack, grid, and place in the same container.perl
# pack — flow layout
$w->pack(-side=>"top", -fill=>"x", -expand=>1, -padx=>5);
# grid — table layout (best for forms)
$w->grid(-row=>0, -column=>1, -sticky=>"ew", -padx=>5);
$parent->gridColumnconfigure(1, -weight=>1);
# place — absolute
$w->place(-relx=>0.5, -rely=>0.5, -anchor=>"center");§25
Events, Bindings & Callbacks
perl
$w->bind("", \&on_click);
$w->bind("", \&on_enter);
$canvas->bind("", sub {
my $e = $canvas->XEvent;
printf "x=%d y=%d\n", $e->x, $e->y;
}); Keyboard Shortcuts — FreeBSD/X11
perl
# FreeBSD/X11: standard modifier is Control, not Meta/Cmd
$mw->bind("", \&save); # Ctrl+S
$mw->bind("", \&open_file); # Ctrl+O
$mw->bind("", \&undo); # Ctrl+Z
$mw->bind("", sub { exit }); # Ctrl+Q
# Meta on X11 maps to the Alt key
$mw->bind("", sub { exit }); # Alt+F4 Control vs Meta on X11:
<Control-key> = Ctrl. <Meta-key> = Alt on most X11 systems. The macOS Command key does not exist in FreeBSD/X11.after() Timer
perl
sub tick {
update_clock();
$mw->after(1000, \&tick); # Re-schedule every second
}
tick(); # Start the clock§26
Menus & Dialogs
perl
my $mb = $mw->Menu; $mw->configure(-menu=>$mb);
my $file = $mb->cascade(-label=>"File", -tearoff=>0);
$file->command(-label=>"Open…", -accelerator=>"Ctrl+O", -command=>\&open_f);
$file->command(-label=>"Quit", -accelerator=>"Ctrl+Q", -command=>sub{exit});
my $ans = $mw->messageBox(-type=>"YesNo",-message=>"Sure?");
my $f = $mw->getOpenFile(-filetypes=>[["Text",".txt"],["All","*"]]);
my $c = $mw->chooseColor(-initialcolor=>"#2060c0");§27
The Canvas Widget
perl
my $cv = $mw->Canvas(-width=>600,-height=>400,-background=>"white")
->pack(-fill=>"both",-expand=>1);
$cv->createLine(10,10,200,100, -fill=>"blue",-width=>2);
$cv->createRectangle(50,50,200,150, -fill=>"lightblue",-outline=>"navy");
$cv->createOval(250,50,400,200, -fill=>"yellow");
$cv->createText(300,250, -text=>"Hello!",-font=>"Helvetica 16 bold");
$cv->move($id,10,5); # Move
$cv->itemconfigure($id,-fill=>"red"); # Recolour
$cv->delete("all"); # Clear§28
Building a Complete Application
perl
#!/usr/local/bin/perl
use strict; use warnings; use Tk;
my ($modified,$status) = (0,"Ready");
my $mw = MainWindow->new;
$mw->title("My App"); $mw->geometry("800x600");
$mw->protocol("WM_DELETE_WINDOW", \&on_quit);
build_menu($mw); build_body($mw);
$mw->bind("", \&cmd_save); # Ctrl+S — FreeBSD/X11
$mw->bind("", \&on_quit); # Ctrl+Q
MainLoop;
sub on_quit {
if ($modified) {
my $a = $mw->messageBox(-type=>"YesNoCancel",-message=>"Save?");
return if $a eq "Cancel";
cmd_save() if $a eq "Yes";
}
exit;
} §29
FreeBSD/Lima-Specific Tips & Best Practices
File Transfers (no automount in FreeBSD 16)
bash
PORT=$(limactl list --format '{{.SSHLocalPort}}' experimental/freebsd-16)
KEY=~/.lima/_config/user
# macOS → VM
scp -P $PORT -i $KEY *.pl 127.0.0.1:~/
# VM → macOS
scp -P $PORT -i $KEY 127.0.0.1:~/output.txt ~/Downloads/
# Sync directory with rsync
rsync -av -e "ssh -p $PORT -i $KEY" ./scripts/ 127.0.0.1:~/scripts/X11 Troubleshooting
bash
open -a XQuartz # Start XQuartz on macOS
echo $DISPLAY # Check: should be localhost:10.0
export DISPLAY=localhost:10.0 # Set manually if empty
pkg install xterm && xterm & # Test X11 with a simple windowFonts on FreeBSD X11
perl
-font => "Helvetica 13" # Always available (X11 core)
-font => "Courier 12" # Always available
-font => "DejaVu Sans 12" # pkg install dejavu-ttc
-font => "DejaVu Sans Mono 12" # Excellent monospacepkg Quick Reference
bash
pkg update # Refresh catalogue
pkg install p5-Tk # Install package
pkg search perl # Search
pkg info perl5 # Details
pkg upgrade # Upgrade all⚡ Complete Daily Workflow
bash
# ── One-time (macOS host) ──────────────────────────────────────
brew install lima qemu && brew install --cask xquartz
limactl start template:experimental/freebsd-16
# ── One-time (inside FreeBSD VM) ──────────────────────────────
pkg update && pkg install perl5 p5-Tk xorg-fonts-truetype dejavu-ttc
# ── Daily ─────────────────────────────────────────────────────
PORT=$(limactl list --format '{{.SSHLocalPort}}' experimental/freebsd-16)
scp -P $PORT -i ~/.lima/_config/user *.pl 127.0.0.1:~/
ssh -X -p $PORT -i ~/.lima/_config/user -o StrictHostKeyChecking=no 127.0.0.1
# Inside VM:
perl perl_gui_04_app.pl # PerlPad opens in XQuartz window