Perl on a Chromebook
Full Perl 5 development — CLI tools, GUI apps, and web services — running natively in the ChromeOS Linux container.
Overview & Your Options
A modern Chromebook is a capable Perl development machine. ChromeOS ships with a built-in Debian Linux container that runs Perl, all CPAN modules, GUI toolkits, and web servers — with GUI windows appearing natively as ChromeOS windows via XWayland. No virtual machine manager, no separate X11 server, no special setup is required.
Why the Linux Container is the Right Choice
| Feature | Linux Container | Termux | Cloud IDE |
|---|---|---|---|
| Perl version | 5.36+ (current Debian) | 5.36+ | Varies |
| CPAN modules | All — apt + cpanm | Most — cpanm | Pre-installed set |
| GUI apps (Tk, Gtk3) | ✓ Native XWayland windows | ✗ CLI only | ✗ None |
| File access | ✓ Chrome Files app integration | Limited | Browser upload |
| Offline use | ✓ Fully offline | ✓ | ✗ Internet needed |
| VS Code / editors | ✓ Full desktop IDE | nano/vim only | Web-based |
Enable the Linux Container
ChromeOS's Linux container (codenamed Crostini) runs a real Debian Linux environment in a secure container. Its apps appear as regular ChromeOS windows, and its files appear in the Files app.
Click the clock in the bottom-right corner → click the gear icon → or press Alt Shift S and click Settings.
In Settings, scroll down to Advanced → Developers → Linux development environment → click Turn on.
Choose a disk size (10–20 GB recommended for Perl development with GUI apps). Click Install. The container downloads and configures itself — this takes 3–10 minutes on first run.
Once setup completes, a terminal window opens automatically. You can reopen it anytime from the app launcher — search for Terminal or look for the penguin icon. You are now in a Debian Linux shell.
whoami # Your Linux username (matches your Google account)
uname -a # Linux kernel version
cat /etc/debian_version # Should show 12.x (Bookworm)
ls ~/ # Your home directory — empty to startIn ChromeOS Files app, right-click Downloads and select Share with Linux. Your Downloads folder then appears at /mnt/chromeos/MyFiles/Downloads in Linux — easy file transfer between ChromeOS and the container.
Install Perl & Development Tools
The container already has a minimal Perl, but you need the full package with docs, build tools, and a module installer. Run these commands in the Terminal:
sudo apt update && sudo apt upgrade -ysudo apt install -y \
perl \
perl-doc \
perldoc \
cpanminus \
build-essential \
libssl-dev \
libexpat1-dev
# Verify
perl -v # Should show v5.36 or v5.38
which cpanm # /usr/bin/cpanm
perldoc perl # Opens the Perl manual (press q to exit)sudo apt install -y \
perl-tk \
libgtk3-perl \
libwx-perl \
libglib-perl
# Test Perl/Tk (will show a small window via XWayland)
perl -e '
use Tk;
my $mw = MainWindow->new;
$mw->title("Perl/Tk works!");
$mw->Label(-text => "Hello from Chromebook!")->pack(-pady => 20);
$mw->Button(-text => "Close", -command => sub { exit })->pack;
MainLoop;
'cpanm Mojolicious
cpanm Plack
cpanm LWP::UserAgent
# Verify Mojolicious
perl -e 'use Mojolicious; print "Mojo $Mojolicious::VERSION\n"'cpanm \
Modern::Perl \
Try::Tiny \
Path::Tiny \
DateTime \
JSON::PP \
Text::CSV \
DBI \
DBD::SQLitecpanm fails to build a module, try sudo apt install p5-module-name first — many CPAN modules have pre-built Debian packages.Editors & IDE Setup
VS Code (Recommended)
VS Code for Linux installs inside the container and appears as a ChromeOS app. It's the best option for Perl development on Chromebook.
# Method 1: apt repository (recommended — keeps it updated)
sudo apt install -y wget gpg
wget -qO- https://packages.microsoft.com/keys/microsoft.asc \
| gpg --dearmor > /tmp/packages.microsoft.gpg
sudo install -D -o root -g root -m 644 \
/tmp/packages.microsoft.gpg \
/etc/apt/keyrings/packages.microsoft.gpg
sudo sh -c 'echo "deb [arch=amd64,arm64,armhf \
signed-by=/etc/apt/keyrings/packages.microsoft.gpg] \
https://packages.microsoft.com/repos/code stable main" \
> /etc/apt/sources.list.d/vscode.list'
sudo apt update && sudo apt install -y code
# Launch it
code # Opens VS Code as a ChromeOS window
code myfile.pl # Open a specific fileVS Code Perl Extensions
Open VS Code, press CtrlShiftX, and install these extensions:
bmewburn.perl-navigator — Syntax checking, auto-complete, go-to definition.
richterger.perl — IntelliSense, function signatures.
Linting via perlcritic and perltidy formatting.
Code style checker integration.
cpanm Perl::Critic
cpanm Perl::Tidy
sudo apt install -y perl-debug # Enable the built-in Perl debuggerGedit — Lightweight GUI Editor
sudo apt install -y gedit gedit-plugins
# Enable Perl syntax highlighting in Edit → Preferences → Plugins
gedit myfile.pl & # Opens as ChromeOS windowVim / Neovim in the Terminal
sudo apt install -y neovim
# For Perl syntax and autocomplete in Neovim:
cpanm Neovim::Ext # Perl host provider for Neovim pluginsYour Project Directory
mkdir -p ~/perl_projects/hello_world
mkdir -p ~/perl_projects/gui_apps
mkdir -p ~/perl_projects/web_apps
# This directory appears in the ChromeOS Files app under "Linux files"
# You can open any .pl file from there with a right-click → Open with Codehello_world/
hello.pl
system_info.pl
gui_apps/
tk_notes.pl
gtk3_calculator.pl
web_apps/
app.pl
templates/
Makefile.PL
Development Workflow
Running Scripts
perl myscript.pl # Run a script
perl -c myscript.pl # Syntax-check only (no execution)
perl -w myscript.pl # Extra warnings
perl -e 'print "Hello\n";' # One-liner
perl -n -e 'print if /error/i' logfile.txt # One-liner filter
chmod +x myscript.pl # Make executable
./myscript.pl # Run directly (needs #!/usr/bin/perl shebang)
# Debugging
perl -d myscript.pl # Interactive debugger
perl -d:Trace myscript.pl # Trace every line executedChrome OS ↔ Linux File Sharing
# ChromeOS Downloads folder (if shared with Linux):
ls /mnt/chromeos/MyFiles/Downloads/
# Copy a .pl file from Downloads to your project folder:
cp /mnt/chromeos/MyFiles/Downloads/script.pl ~/perl_projects/
# Save output to Downloads (opens in ChromeOS Files):
perl myscript.pl > /mnt/chromeos/MyFiles/Downloads/output.txtThe Interactive REPL — Reply
cpanm Reply # Install a Perl REPL (like irb for Ruby)
reply # Start interactive Perl shell
# Type any Perl expression and see the result immediately:
# > 2 ** 32
# 4294967296
# > "hello" . "world"
# helloworldManaging Modules with cpanm
cpanm Module::Name # Install
cpanm --uninstall Module::Name # Remove
cpanm --info Module::Name # Version info
cpanm --look Module::Name # Browse source
perl -MModule::Name -e 'print Module::Name->VERSION, "\n"' # Check versionHello World — Your First Perl Script
mkdir -p ~/perl_projects/hello_world
cd ~/perl_projects/hello_world
nano hello.pl # or: code hello.pl#!/usr/bin/perl
# =============================================================================
# hello.pl — Your first Perl script on a Chromebook
# Run with: perl hello.pl
# =============================================================================
use strict;
use warnings;
# String output
print "Hello, Chromebook!\n";
# Formatted output with printf
my $name = "Perl";
my $year = (localtime)[5] + 1900;
printf "Welcome to %s development in %d.\n", $name, $year;
# Show where we're running
printf "Running on: %s\n", $^O; # linux
printf "Perl version: %s\n", $]; # 5.038002 etc.
printf "Home dir: %s\n", $ENV{HOME}; # /home/penguin
# Array
my @tools = qw(VS-Code Terminal nano vim);
print "\nYour Linux tools:\n";
printf " %d. %s\n", $_ + 1, $tools[$_] for 0 .. $#tools;
# Hash
my %info = (
OS => "ChromeOS Linux (Crostini)",
Distro => "Debian",
Shell => $ENV{SHELL} // "bash",
Editor => "VS Code",
);
print "\nEnvironment:\n";
printf " %-10s : %s\n", $_, $info{$_} for sort keys %info;
print "\nSetup complete — happy Perl coding!\n";perl hello.plCLI Scripts — Practical Tools
Perl excels at command-line tools. These scripts are immediately useful for Chromebook work.
Script 1: System Information Reporter
#!/usr/bin/perl
# =============================================================================
# sysinfo.pl — Chromebook Linux system information reporter
# Run with: perl sysinfo.pl
# =============================================================================
use strict;
use warnings;
use POSIX qw(uname);
use List::Util qw(sum);
sub divider { print "-" x 50, "\n" }
sub heading { divider(); print " $_[0]\n"; divider() }
# ── Kernel & OS ───────────────────────────────────────────────────────────────
heading("System");
my @un = uname();
printf " %-16s %s\n", "Kernel:", $un[2];
printf " %-16s %s\n", "Architecture:", $un[4];
printf " %-16s %s\n", "Hostname:", $un[1];
if (open my $fh, "<", "/etc/os-release") {
while (<$fh>) {
if (/^PRETTY_NAME="(.+)"/) {
printf " %-16s %s\n", "OS:", $1;
last;
}
}
}
# ── Perl environment ──────────────────────────────────────────────────────────
heading("Perl");
printf " %-16s %vd\n", "Version:", $^V;
printf " %-16s %s\n", "Executable:", $^X;
printf " %-16s %s\n", "Platform:", $^O;
# Count installed modules
my $mod_count = 0;
for my $dir (@INC) {
next unless -d $dir;
opendir(my $dh, $dir) or next;
$mod_count += grep { /\.pm$/i } readdir($dh);
closedir($dh);
}
printf " %-16s %d\n", "Modules (.pm):", $mod_count;
# ── Memory ────────────────────────────────────────────────────────────────────
heading("Memory");
if (open my $mf, "<", "/proc/meminfo") {
my %mem;
while (<$mf>) {
$mem{$1} = $2 if /^(\w+):\s+(\d+)/;
}
my $total = $mem{MemTotal} // 0;
my $free = $mem{MemAvailable} // 0;
my $used = $total - $free;
printf " %-16s %.1f GB\n", "Total:", $total / 1_048_576;
printf " %-16s %.1f GB\n", "Used:", $used / 1_048_576;
printf " %-16s %.1f GB\n", "Available:", $free / 1_048_576;
}
# ── Disk ──────────────────────────────────────────────────────────────────────
heading("Disk — Home Partition");
my $df = `df -BM "$ENV{HOME}" 2>/dev/null | tail -1`;
if ($df =~ /(\d+)M\s+(\d+)M\s+(\d+)M/) {
printf " %-16s %d GB\n", "Total:", $1 / 1024;
printf " %-16s %d GB\n", "Used:", $2 / 1024;
printf " %-16s %d GB\n", "Free:", $3 / 1024;
}
# ── Environment ───────────────────────────────────────────────────────────────
heading("Environment");
for my $var (qw(DISPLAY TERM SHELL HOME LANG)) {
printf " %-16s %s\n", "$var:", $ENV{$var} // "(not set)";
}
divider();
print " Running in: ";
print -d "/run/user" ? "container\n" : "native Linux\n";Script 2: Chromebook Linux Files Analyzer
#!/usr/bin/perl
# =============================================================================
# files.pl — Analyze a directory of files
# Usage: perl files.pl [directory]
# Run on your home dir: perl files.pl ~
# =============================================================================
use strict;
use warnings;
use File::Find;
use File::Basename;
use POSIX qw(floor);
use List::Util qw(sum max);
my $dir = shift // $ENV{HOME};
my $hidden = grep { /^-a/ } @ARGV; # -a flag shows hidden files
die "Directory '$dir' not found.\n" unless -d $dir;
my %stats;
my @large;
my $start = time;
find({
no_chdir => 1,
wanted => sub {
return unless -f $_;
return if !$hidden && m{/\.}; # Skip hidden unless -a
my $size = -s $_;
my (undef, undef, $ext) = fileparse($_, qr/\.[^.]*/);
$ext = lc($ext) || '(no ext)';
$stats{count}++;
$stats{total_bytes} += $size;
$stats{by_ext}{$ext}{count}++;
$stats{by_ext}{$ext}{bytes} += $size;
push @large, [$_, $size] if $size > 1_048_576; # > 1 MB
},
}, $dir);
my $elapsed = time - $start;
# ── Summary ───────────────────────────────────────────────────────────────────
my $total_mb = ($stats{total_bytes} // 0) / 1_048_576;
printf "\n%-24s %s\n", "Directory:", $dir;
printf "%-24s %d\n", "Files found:", $stats{count} // 0;
printf "%-24s %.1f MB\n","Total size:", $total_mb;
printf "%-24s %ds\n\n", "Scan time:", $elapsed;
# ── By extension ─────────────────────────────────────────────────────────────
print "By extension (top 15):\n";
printf " %-12s %6s %10s\n", "Extension", "Files", "Size";
printf " %s\n", "-" x 35;
my @sorted_ext = sort {
$stats{by_ext}{$b}{bytes} <=> $stats{by_ext}{$a}{bytes}
} keys %{ $stats{by_ext} };
for my $ext (@sorted_ext[0..14]) {
last unless defined $ext;
my $c = $stats{by_ext}{$ext}{count};
my $b = $stats{by_ext}{$ext}{bytes};
printf " %-12s %6d %8.1f MB\n", $ext, $c, $b / 1_048_576;
}
# ── Large files ───────────────────────────────────────────────────────────────
if (@large) {
print "\nFiles larger than 1 MB:\n";
for my $f (sort { $b->[1] <=> $a->[1] } @large) {
printf " %8.1f MB %s\n", $f->[1] / 1_048_576,
substr($f->[0], length($dir) + 1);
}
}File Processing Scripts
CSV Report Generator
A practical script showing how to read CSV files, process data, and produce reports — useful for handling exported spreadsheet data in ChromeOS.
#!/usr/bin/perl
# =============================================================================
# csv_report.pl — CSV file analyzer and report generator
# Usage: perl csv_report.pl data.csv
# perl csv_report.pl (uses built-in demo data)
# Modules: Text::CSV (cpanm Text::CSV)
# =============================================================================
use strict;
use warnings;
use List::Util qw(sum min max);
# ── Parse a simple CSV (no quotes/escaping for clarity) ──────────────────────
sub parse_csv_line {
my ($line) = @_;
chomp $line;
return map { s/^\s+|\s+$//gr } split(/,/, $line);
}
# ── Demo data (sales records) ────────────────────────────────────────────────
my @demo_csv = (
"Product,Category,Units,Price",
"Widget Pro,Electronics,42,29.99",
"Gadget Lite,Electronics,87,14.99",
"Desk Lamp,Home,31,45.00",
"USB Hub,Electronics,124,19.99",
"Notebook,Stationery,200,5.99",
"Pen Set,Stationery,315,8.49",
"Monitor Stand,Home,28,79.99",
"Keyboard,Electronics,56,49.99",
"Mouse Pad,Electronics,93,12.99",
"Bookend Set,Home,44,24.99",
);
# Use file if given, else demo
my @lines;
if (@ARGV && -f $ARGV[0]) {
open(my $fh, "<", $ARGV[0]) or die "Cannot open: $!";
@lines = <$fh>;
close $fh;
} else {
@lines = @demo_csv;
print "(Using demo data — pass a CSV file as argument)\n\n";
}
# ── Parse header and rows ─────────────────────────────────────────────────────
my @header = parse_csv_line(shift @lines);
printf "Columns: %s\n\n", join(", ", @header);
my @rows;
for my $line (@lines) {
my @fields = parse_csv_line($line);
next unless @fields == @header;
my %row;
@row{@header} = @fields;
push @rows, \%row;
}
printf "Records: %d\n\n", scalar @rows;
# ── Analysis ──────────────────────────────────────────────────────────────────
# Assume numeric columns are the last two (Units, Price)
my ($unit_col, $price_col) = @header[-2, -1];
my $name_col = $header[0];
my $cat_col = $header[1];
# Calculate revenue per row
my %by_category;
my @revenues;
for my $r (@rows) {
my $rev = ($r->{$unit_col} // 0) * ($r->{$price_col} // 0);
$r->{Revenue} = $rev;
push @revenues, $rev;
$by_category{ $r->{$cat_col} }{count}++;
$by_category{ $r->{$cat_col} }{revenue} += $rev;
$by_category{ $r->{$cat_col} }{units} += $r->{$unit_col} // 0;
}
# ── Detailed table ────────────────────────────────────────────────────────────
printf "%-22s %-14s %7s %8s %10s\n",
$name_col, $cat_col, $unit_col, $price_col, "Revenue";
print "-" x 65, "\n";
for my $r (sort { $b->{Revenue} <=> $a->{Revenue} } @rows) {
printf "%-22s %-14s %7d %8.2f %10.2f\n",
$r->{$name_col}, $r->{$cat_col},
$r->{$unit_col} // 0, $r->{$price_col} // 0, $r->{Revenue};
}
print "-" x 65, "\n";
printf "%-22s %-14s %7d %8s %10.2f\n",
"TOTAL", "", sum(map {$_->{$unit_col}} @rows),
"", sum(@revenues);
# ── Summary by category ───────────────────────────────────────────────────────
print "\nBy Category:\n";
for my $cat (sort { $by_category{$b}{revenue} <=> $by_category{$a}{revenue} }
keys %by_category) {
printf " %-14s %3d products %6d units \$%8.2f revenue\n",
$cat,
$by_category{$cat}{count},
$by_category{$cat}{units},
$by_category{$cat}{revenue};
}
printf "\nTotal revenue: \$%.2f\n", sum(@revenues);
printf "Best product: %s (\$%.2f)\n",
(sort { $b->{Revenue} <=> $a->{Revenue} } @rows)[0]{$name_col},
max(@revenues);Modules & CPAN on Chromebook
Essential Modules for Chromebook Perl
| Category | Module | Install | Use |
|---|---|---|---|
| Modern Perl | Modern::Perl | cpanm | Enables strict, warnings, say, etc. |
| Error handling | Try::Tiny | cpanm | try/catch/finally blocks |
| File paths | Path::Tiny | cpanm | Easy file/dir manipulation |
| JSON | JSON::PP | Built-in | JSON encode/decode |
| HTTP client | LWP::UserAgent | cpanm | HTTP requests |
| CSV | Text::CSV | cpanm | Read/write CSV files |
| Dates | DateTime | cpanm | Date/time math and formatting |
| Database | DBI + DBD::SQLite | cpanm | SQLite database access |
| Testing | Test::Simple | Built-in | Unit testing |
#!/usr/bin/perl
use strict; use warnings;
# Modern::Perl enables useful features
use Modern::Perl '2023'; # Enables say, state, strict, warnings
say "say adds a newline automatically"; # Like print "\n"
state $count = 0; # Persistent variable across calls
# Try::Tiny — clean error handling
use Try::Tiny;
try {
die "something went wrong\n";
} catch {
warn "Caught: $_";
} finally {
say "This always runs";
};
# Path::Tiny — easy file operations
use Path::Tiny;
my $dir = path("~")->absolute; # Resolve ~ to full path
my @pls = $dir->children(qr/\.pl$/); # Find all .pl files
say "Perl scripts: " . scalar(@pls);
my $file = path("/tmp/test.txt");
$file->spew("Hello from Path::Tiny!\n"); # Write
say $file->slurp; # Read
$file->remove; # Delete
# JSON::PP — no install needed
use JSON::PP;
my $data = { name => "Chromebook", year => 2024, active => \1 };
my $json = encode_json($data);
say "JSON: $json";
my $back = decode_json($json);
say "Name: $back->{name}";
# DBD::SQLite — in-container database
use DBI;
my $dbh = DBI->connect("dbi:SQLite:dbname=/tmp/test.db", "", "");
$dbh->do("CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, text TEXT)");
$dbh->do("INSERT INTO notes (text) VALUES (?)", undef, "First note from Chromebook!");
my $rows = $dbh->selectall_arrayref("SELECT * FROM notes");
printf "DB record: %d — %s\n", $_->[0], $_->[1] for @$rows;
$dbh->disconnect;
unlink "/tmp/test.db";GUI Development on Chromebook
This is where Chromebook Linux shines: GUI apps from the Linux container run as first-class ChromeOS windows. They appear in the taskbar, can be pinned to the shelf, support full-screen mode, and integrate with the ChromeOS window manager — with no X11 server configuration needed.
DISPLAY variable is set to :0 automatically when you open a Terminal.# Verify DISPLAY is set (should be :0 or similar)
echo $DISPLAY
# If empty, set it manually:
export DISPLAY=:0
# Quick GUI test — a bare Perl/Tk window
perl -e '
use Tk;
my $mw = MainWindow->new;
$mw->title("Chromebook GUI Test");
$mw->geometry("300x120");
$mw->Label(-text => "✓ GUI works on ChromeOS!",
-font => "Helvetica 14 bold",
-fg => "#34c759")->pack(-pady => 20);
$mw->Button(-text => "Close", -command => sub { exit })->pack;
MainLoop;
'Making Linux Apps Appear in the Launcher
mkdir -p ~/.local/share/applications
cat > ~/.local/share/applications/perl-notes.desktop << 'EOF'
[Desktop Entry]
Name=Perl Notes
Comment=Perl/Tk Notes Application
Exec=perl /home/penguin/perl_projects/gui_apps/tk_notes.pl
Icon=text-editor
Terminal=false
Type=Application
Categories=Utility;
EOF
# Refresh the app list
update-desktop-database ~/.local/share/applicationsAfter this, Perl Notes appears in the ChromeOS launcher alongside all other apps.
Perl/Tk App — Chromebook Notes
A complete Perl/Tk sticky-notes application: create notes, store them as text files in your Linux home directory (visible in the ChromeOS Files app), and load them back. Demonstrates the full Chromebook development cycle.
#!/usr/bin/perl
# =============================================================================
# tk_notes.pl — Chromebook Notes (Perl/Tk)
# Install: sudo apt install perl-tk
# Run: perl tk_notes.pl
#
# Notes are saved in ~/notes/ and are visible in the ChromeOS Files app.
# =============================================================================
use strict;
use warnings;
use Tk;
use Tk::Text;
use Tk::Listbox;
use File::Basename;
use Cwd qw(abs_path);
# ── State ──────────────────────────────────────────────────────────────────────
my $notes_dir = "$ENV{HOME}/notes";
my $current_note = "";
my $modified = 0;
my $status_msg = "Ready";
mkdir $notes_dir unless -d $notes_dir;
# ── Main window ───────────────────────────────────────────────────────────────
my $mw = MainWindow->new;
$mw->title("Chromebook Notes");
$mw->geometry("760x560+60+40");
$mw->configure(-background => "#1e1e2e");
$mw->protocol("WM_DELETE_WINDOW", \&on_quit);
# ── Menu bar ──────────────────────────────────────────────────────────────────
my $menu = $mw->Menu;
$mw->configure(-menu => $menu);
my $file = $menu->cascade(-label => "File", -tearoff => 0);
$file->command(-label => "New Note", -accelerator => "Ctrl+N", -command => \&cmd_new);
$file->command(-label => "Save", -accelerator => "Ctrl+S", -command => \&cmd_save);
$file->command(-label => "Delete Note", -command => \&cmd_delete);
$file->separator;
$file->command(-label => "Quit", -accelerator => "Ctrl+Q", -command => \&on_quit);
my $edit = $menu->cascade(-label => "Edit", -tearoff => 0);
$edit->command(-label => "Cut", -accelerator => "Ctrl+X", -command => sub { $mw->focus; $editor->event_generate("<>") });
$edit->command(-label => "Copy", -accelerator => "Ctrl+C", -command => sub { $editor->event_generate("<>") });
$edit->command(-label => "Paste", -accelerator => "Ctrl+V", -command => sub { $editor->event_generate("<>") });
# ── Layout: sidebar (notes list) + editor ─────────────────────────────────────
my $paned = $mw->Frame(-background => "#1e1e2e")->pack(-fill => "both", -expand => 1);
# Sidebar
my $sidebar = $paned->Frame(
-background => "#16161e",
-borderwidth => 0,
-width => 200,
)->pack(-side => "left", -fill => "y");
$sidebar->packPropagate(0);
$sidebar->Label(
-text => "MY NOTES",
-font => "Helvetica 9",
-foreground => "#5bc7f0",
-background => "#16161e",
-anchor => "w",
)->pack(-fill => "x", -padx => 10, -pady => 8);
# Notes list with scrollbar
my $lb_frame = $sidebar->Frame(-background => "#16161e")->pack(-fill => "both", -expand => 1);
my $lb = $lb_frame->Listbox(
-background => "#16161e",
-foreground => "#e2d9c8",
-selectbackground => "#2a3a5a",
-selectforeground => "#5bc7f0",
-font => "Helvetica 11",
-borderwidth => 0,
-relief => "flat",
-activestyle => "none",
)->pack(-side => "left", -fill => "both", -expand => 1);
my $lb_scroll = $lb_frame->Scrollbar(-command => [$lb, "yview"], -width => 8)->pack(
-side => "right", -fill => "y"
);
$lb->configure(-yscrollcommand => [$lb_scroll, "set"]);
# Sidebar buttons
my $btn_row = $sidebar->Frame(-background => "#16161e")->pack(-fill => "x", -padx => 8, -pady => 6);
$btn_row->Button(
-text => "+ New",
-font => "Helvetica 10",
-background => "#2a4a8a",
-foreground => "white",
-relief => "flat",
-padx => 6, -pady => 3,
-command => \&cmd_new,
)->pack(-side => "left", -padx => 2);
$btn_row->Button(
-text => "⌫ Delete",
-font => "Helvetica 10",
-background => "#4a1a1a",
-foreground => "#e08080",
-relief => "flat",
-padx => 6, -pady => 3,
-command => \&cmd_delete,
)->pack(-side => "left", -padx => 2);
# ── Editor area ────────────────────────────────────────────────────────────────
my $editor_frame = $paned->Frame(-background => "#1e1e2e")->pack(
-side => "left", -fill => "both", -expand => 1
);
my $title_var = "";
my $title_entry = $editor_frame->Entry(
-textvariable => \$title_var,
-font => "Helvetica 14 bold",
-background => "#2a2a3e",
-foreground => "#e2d9c8",
-insertbackground => "white",
-relief => "flat",
-borderwidth => 4,
)->pack(-fill => "x", -padx => 8, -pady => 6);
my $editor_box = $editor_frame->Frame(-background => "#1e1e2e")->pack(
-fill => "both", -expand => 1, -padx => 8
);
my $editor = $editor_box->Text(
-font => "Helvetica 12",
-background => "#1e1e2e",
-foreground => "#e2d9c8",
-insertbackground => "white",
-selectbackground => "#264f78",
-relief => "flat",
-padx => 4, -pady => 4,
-wrap => "word",
-undo => 1,
)->pack(-side => "left", -fill => "both", -expand => 1);
my $ed_scroll = $editor_box->Scrollbar(-command => [$editor, "yview"], -width => 8)->pack(
-side => "right", -fill => "y"
);
$editor->configure(-yscrollcommand => [$ed_scroll, "set"]);
# Toolbar below editor
my $toolbar = $editor_frame->Frame(-background => "#16161e")->pack(-fill => "x", -pady => 4);
for my $info (
["Save Ctrl+S", \&cmd_save, "#2a4a2a", "#6fcf97"],
["Undo Ctrl+Z", sub { $editor->edit_undo }, "#2a2a3a", "#9b9380"],
) {
my ($label, $cb, $bg, $fg) = @$info;
$toolbar->Button(
-text => $label, -command => $cb,
-background => $bg, -foreground => $fg,
-font => "Helvetica 10", -relief => "flat",
-padx => 10, -pady => 4,
)->pack(-side => "left", -padx => 4);
}
# Status bar
my $status_bar = $mw->Frame(-background => "#5bc7f0", -height => 22)->pack(-fill => "x", -side => "bottom");
$status_bar->Label(
-textvariable => \$status_msg,
-font => "Helvetica 10",
-foreground => "#0a1a2a",
-background => "#5bc7f0",
-anchor => "w",
)->pack(-side => "left", -padx => 10);
$status_bar->Label(
-text => "Notes dir: $notes_dir",
-font => "Helvetica 10",
-foreground => "#0a1a2a",
-background => "#5bc7f0",
)->pack(-side => "right", -padx => 10);
# ── Keyboard shortcuts ─────────────────────────────────────────────────────────
$mw->bind("", \&cmd_new);
$mw->bind("", \&cmd_save);
$mw->bind("", \&on_quit);
# ── Track modifications ────────────────────────────────────────────────────────
$editor->bind("<>", sub {
if ($editor->edit_modified) {
$modified = 1;
$mw->title("Chromebook Notes •");
}
});
# ── Listbox selection ──────────────────────────────────────────────────────────
$lb->bind("<>", sub {
my ($idx) = $lb->curselection;
return unless defined $idx;
my $name = $lb->get($idx);
load_note($name);
});
# ── Load note list on start ────────────────────────────────────────────────────
refresh_list();
# ═══════════════════════════════════════════════════════════════════════
# Subroutines
# ═══════════════════════════════════════════════════════════════════════
sub refresh_list {
$lb->delete(0, "end");
my @notes = sort glob("$notes_dir/*.txt");
for my $f (@notes) {
$lb->insert("end", basename($f, ".txt"));
}
}
sub load_note {
my ($name) = @_;
my $path = "$notes_dir/$name.txt";
return unless -f $path;
if ($modified) {
my $ans = $mw->messageBox(
-type => "YesNo", -icon => "question",
-message => "Save current note first?"
);
cmd_save() if $ans eq "Yes";
}
open(my $fh, "<", $path) or return;
my $content = do { local $/; <$fh> };
close($fh);
my $title = (split /\n/, $content, 2)[0] =~ s/^#\s*//r;
$title_var = $title;
$editor->delete("1.0", "end");
$editor->insert("1.0", $content);
$editor->edit_modified(0);
$current_note = $name;
$modified = 0;
$mw->title("Chromebook Notes — $name");
$status_msg = "Loaded: $name";
}
sub cmd_new {
my $name = "note_" . time;
$title_var = "New Note";
$editor->delete("1.0", "end");
$editor->insert("1.0", "# New Note\n\n");
$editor->edit_modified(0);
$current_note = $name;
$modified = 0;
$mw->title("Chromebook Notes — new");
$status_msg = "New note";
$title_entry->focus;
}
sub cmd_save {
my $name = $title_var;
$name =~ s/[^\w\s-]//g;
$name =~ s/\s+/_/g;
$name = lc($name) || $current_note || "untitled_" . time;
my $path = "$notes_dir/$name.txt";
my $content = $editor->get("1.0", "end");
open(my $fh, ">", $path) or do {
$status_msg = "ERROR: cannot save";
return;
};
print $fh $content;
close($fh);
$editor->edit_modified(0);
$current_note = $name;
$modified = 0;
$mw->title("Chromebook Notes — $name");
$status_msg = "Saved: $name.txt";
refresh_list();
}
sub cmd_delete {
my ($idx) = $lb->curselection;
return unless defined $idx;
my $name = $lb->get($idx);
my $ans = $mw->messageBox(
-type => "YesNo", -icon => "question",
-message => "Delete note '$name'?"
);
if ($ans eq "Yes") {
unlink "$notes_dir/$name.txt";
$editor->delete("1.0", "end");
$title_var = "";
$current_note = "";
$modified = 0;
$mw->title("Chromebook Notes");
refresh_list();
$status_msg = "Deleted: $name";
}
}
sub on_quit {
cmd_save() if $modified;
exit;
}
MainLoop; Gtk3 App — System Monitor
A Gtk3 application that displays live CPU, memory, and disk information, updating every second via a GLib timer. Demonstrates Gtk3's grid layout, markup labels, progress bars, and the Glib::Timeout repeating timer.
#!/usr/bin/perl
# =============================================================================
# gtk3_sysmon.pl — Chromebook System Monitor (Gtk3)
# Install: sudo apt install libgtk3-perl
# Run: perl gtk3_sysmon.pl
# =============================================================================
use strict;
use warnings;
use Gtk3 '-init';
use Glib qw(TRUE FALSE);
# ── Read /proc/stat for CPU usage ─────────────────────────────────────────────
my @prev_cpu = (0) x 8;
sub read_cpu_pct {
open my $f, '<', '/proc/stat' or return 0;
my $line = <$f>; close $f;
my (undef, @fields) = split /\s+/, $line;
my $idle = $fields[3] + $fields[4]; # idle + iowait
my $total = 0; $total += $_ for @fields;
my $d_idle = $idle - $prev_cpu[3] - $prev_cpu[4];
my $d_total = $total - $prev_cpu[7];
@prev_cpu = (@fields[0..6], $total);
return 0 if $d_total == 0;
return int(100 - 100 * $d_idle / $d_total);
}
sub read_mem {
open my $f, '<', '/proc/meminfo' or return (0,0);
my %m;
while (<$f>) { $m{$1} = $2 if /^(\w+):\s+(\d+)/ }
close $f;
my $total = $m{MemTotal} // 1;
my $avail = $m{MemAvailable} // 0;
return ($total, $total - $avail);
}
sub read_disk {
my $df = `df -BM "$ENV{HOME}" 2>/dev/null | tail -1`;
return (1,0) unless $df;
my @f = split /\s+/, $df;
return ((($f[1] =~ s/M//r) || 1), (($f[2] =~ s/M//r) || 0));
}
# ── CSS ────────────────────────────────────────────────────────────────────────
my $css = Gtk3::CssProvider->new;
$css->load_from_data(q{
window { background-color: #09090f; }
.title { color: #5bc7f0; font-size: 22px; font-weight: bold; }
.metric { color: #e2d9c8; font-size: 14px; font-family: monospace; }
.label { color: #9b9380; font-size: 12px; }
.value-ok { color: #6fcf97; font-size: 16px; font-weight: bold; font-family: monospace; }
.value-warn { color: #f0a030; font-size: 16px; font-weight: bold; font-family: monospace; }
.value-high { color: #eb5757; font-size: 16px; font-weight: bold; font-family: monospace; }
progressbar trough { min-height: 12px; border-radius: 6px; background: #1e1e30; }
progressbar progress { border-radius: 6px; }
progressbar.ok progress { background: #6fcf97; }
progressbar.warn progress { background: #f0a030; }
progressbar.high progress { background: #eb5757; }
.card { background-color: #0f0f1c; border-radius: 8px; padding: 16px; }
separator { background: #1e1e30; }
});
Gtk3::StyleContext::add_provider_for_screen(
Gtk3::Gdk::Screen::get_default(),
$css, Gtk3::STYLE_PROVIDER_PRIORITY_APPLICATION
);
# ── Main window ───────────────────────────────────────────────────────────────
my $win = Gtk3::Window->new('toplevel');
$win->set_title('Chromebook System Monitor');
$win->set_default_size(440, 360);
$win->set_resizable(FALSE);
$win->signal_connect(destroy => sub { Gtk3->main_quit });
my $root = Gtk3::Box->new('vertical', 0);
$root->set_margin_start(20); $root->set_margin_end(20);
$root->set_margin_top(16); $root->set_margin_bottom(16);
$win->add($root);
# Title
my $title = Gtk3::Label->new('');
$title->set_markup('System Monitor');
$title->set_margin_bottom(16);
$root->pack_start($title, FALSE, FALSE, 0);
# ── Build a metric card ────────────────────────────────────────────────────────
my (%bars, %val_labels, %pct_labels);
sub make_card {
my ($parent, $id, $title_text, $unit) = @_;
my $frame = Gtk3::Frame->new;
$frame->get_style_context->add_class('card');
$frame->set_shadow_type('none');
$frame->set_margin_bottom(10);
$parent->pack_start($frame, FALSE, FALSE, 0);
my $vbox = Gtk3::Box->new('vertical', 6);
$vbox->set_margin_start(16); $vbox->set_margin_end(16);
$vbox->set_margin_top(12); $vbox->set_margin_bottom(12);
$frame->add($vbox);
# Header row: title + value
my $header = Gtk3::Box->new('horizontal', 0);
$vbox->pack_start($header, FALSE, FALSE, 0);
my $lbl = Gtk3::Label->new('');
$lbl->set_markup("$title_text");
$lbl->set_halign('start');
$header->pack_start($lbl, TRUE, TRUE, 0);
my $val = Gtk3::Label->new('— %');
$val->set_halign('end');
$val_labels{$id} = $val;
$header->pack_end($val, FALSE, FALSE, 0);
# Progress bar
my $bar = Gtk3::ProgressBar->new;
$bar->set_fraction(0);
$bar->get_style_context->add_class('ok');
$bars{$id} = $bar;
$vbox->pack_start($bar, FALSE, FALSE, 0);
# Details row
my $details = Gtk3::Label->new('');
$details->set_halign('start');
$details->set_margin_top(2);
$pct_labels{$id} = $details;
$vbox->pack_start($details, FALSE, FALSE, 0);
return $frame;
}
make_card($root, 'cpu', 'CPU', '%');
make_card($root, 'mem', 'Memory', 'GB');
make_card($root, 'disk', 'Disk', 'GB');
# Last-updated label
my $updated = Gtk3::Label->new('');
$updated->set_halign('end');
$updated->set_margin_top(4);
$root->pack_end($updated, FALSE, FALSE, 0);
# ── Update function ────────────────────────────────────────────────────────────
sub update_display {
# CPU
my $cpu_pct = read_cpu_pct();
my $cpu_cls = $cpu_pct > 80 ? 'high' : $cpu_pct > 50 ? 'warn' : 'ok';
my $cpu_col = $cpu_pct > 80 ? '#eb5757' : $cpu_pct > 50 ? '#f0a030' : '#6fcf97';
$bars{cpu}->set_fraction($cpu_pct / 100);
$bars{cpu}->get_style_context->remove_class($_) for qw(ok warn high);
$bars{cpu}->get_style_context->add_class($cpu_cls);
$val_labels{cpu}->set_markup("${cpu_pct}%");
$pct_labels{cpu}->set_markup("Utilisation across all cores");
# Memory
my ($mem_total_kb, $mem_used_kb) = read_mem();
my $mem_pct = $mem_total_kb ? int(100 * $mem_used_kb / $mem_total_kb) : 0;
my $mem_cls = $mem_pct > 85 ? 'high' : $mem_pct > 65 ? 'warn' : 'ok';
my $mem_col = $mem_pct > 85 ? '#eb5757' : $mem_pct > 65 ? '#f0a030' : '#6fcf97';
my $mem_used = sprintf "%.1f", $mem_used_kb / 1_048_576;
my $mem_tot = sprintf "%.1f", $mem_total_kb / 1_048_576;
$bars{mem}->set_fraction($mem_pct / 100);
$bars{mem}->get_style_context->remove_class($_) for qw(ok warn high);
$bars{mem}->get_style_context->add_class($mem_cls);
$val_labels{mem}->set_markup("${mem_pct}%");
$pct_labels{mem}->set_markup("${mem_used} GB used of ${mem_tot} GB");
# Disk
my ($disk_tot, $disk_used) = read_disk();
my $disk_pct = $disk_tot ? int(100 * $disk_used / $disk_tot) : 0;
my $disk_cls = $disk_pct > 90 ? 'high' : $disk_pct > 70 ? 'warn' : 'ok';
my $disk_col = $disk_pct > 90 ? '#eb5757' : $disk_pct > 70 ? '#f0a030' : '#6fcf97';
my $dtg = sprintf "%.1f", $disk_tot / 1024;
my $dug = sprintf "%.1f", $disk_used / 1024;
$bars{disk}->set_fraction($disk_pct / 100);
$bars{disk}->get_style_context->remove_class($_) for qw(ok warn high);
$bars{disk}->get_style_context->add_class($disk_cls);
$val_labels{disk}->set_markup("${disk_pct}%");
$pct_labels{disk}->set_markup("${dug} GB used of ${dtg} GB (home)");
# Timestamp
my $time_str = scalar localtime;
$updated->set_markup("Updated: $time_str");
return TRUE; # Keep the timer running
}
# ── Start timer — update every 1000ms ────────────────────────────────────────
update_display();
Glib::Timeout->add(1000, \&update_display);
$win->show_all;
Gtk3->main;Web Development — Mojolicious
Mojolicious is a full-featured, modern Perl web framework. On a Chromebook, you run the web server in the Linux container and access it from the Chrome browser on the same machine. The server runs at http://localhost:3000 and is visible directly in Chrome — no port forwarding needed.
cpanm Mojolicious
# Quick test:
perl -e 'use Mojolicious; print "Mojo $Mojolicious::VERSION\n"'Minimal Web App
#!/usr/bin/perl
# =============================================================================
# app.pl — Minimal Mojolicious web application
# Run: perl app.pl daemon
# Visit: http://localhost:3000
# =============================================================================
use Mojolicious::Lite -signatures;
# Route: GET /
get '/' => sub ($c) {
$c->render(text => 'Hello from Perl + Mojolicious on your Chromebook!');
};
# Route: GET /hello/:name
get '/hello/:name' => sub ($c) {
my $name = $c->param('name');
$c->render(text => "Hello, $name!");
};
# Route: GET /json
get '/json' => sub ($c) {
$c->render(json => {
message => 'Hello from Perl',
platform => 'Chromebook Linux',
perl => $^V . "",
time => time(),
});
};
app->start;perl app.pl daemon # Starts on http://localhost:3000
# Press Ctrl+C to stopComplete Notes Web App with Templates
#!/usr/bin/perl
# =============================================================================
# notes_web.pl — Mojolicious notes web application
# Run: perl notes_web.pl daemon
# Visit: http://localhost:3000
# Notes stored in: ~/notes/ (same dir as the Tk app)
# =============================================================================
use Mojolicious::Lite -signatures;
use File::Basename;
use Cwd 'abs_path';
my $notes_dir = "$ENV{HOME}/notes";
mkdir $notes_dir unless -d $notes_dir;
# ── Helper: read all notes ────────────────────────────────────────────────────
helper notes => sub ($c) {
my @files = sort glob("$notes_dir/*.txt");
return [ map {
my $name = basename($_, ".txt");
my $preview = "";
if (open my $fh, "<", $_) {
$preview = <$fh> // "";
chomp $preview;
$preview =~ s/^#\s*//;
close $fh;
}
{ name => $name, preview => $preview }
} @files ];
};
# ── Routes ────────────────────────────────────────────────────────────────────
# List all notes
get '/' => sub ($c) {
$c->render(template => 'index', notes => $c->notes);
};
# View a note
get '/note/:name' => sub ($c) {
my $name = $c->param('name');
$name =~ s/[^\w\-]//g;
my $path = "$notes_dir/$name.txt";
my $body = "";
if (open my $fh, "<", $path) { local $/; $body = <$fh>; }
$c->render(template => 'note', name => $name, body => $body);
};
# Save a note (POST)
post '/save' => sub ($c) {
my $name = $c->param('name') // "untitled";
my $body = $c->param('body') // "";
$name =~ s/[^\w\-]//g;
$name ||= "note_" . time;
open(my $fh, ">", "$notes_dir/$name.txt") and do { print $fh $body; close $fh; };
$c->redirect_to("/note/$name");
};
# Delete a note
post '/delete/:name' => sub ($c) {
my $name = $c->param('name');
$name =~ s/[^\w\-]//g;
unlink "$notes_dir/$name.txt";
$c->redirect_to('/');
};
# New note form
get '/new' => sub ($c) {
$c->render(template => 'note', name => 'new_note', body => "# New Note\n\n");
};
app->start;
# ── Inline templates ──────────────────────────────────────────────────────────
__DATA__
@@ layouts/main.html.ep
Chromebook Notes
<%= content %>
@@ index.html.ep
% layout 'main';
Chromebook Notes
<%= scalar @{ $notes } %> note(s) stored in ~/notes/
% if (!@{$notes}) {
% }
@@ note.html.ep
% layout 'main';
% if ($name ne 'new_note') {
% }perl notes_web.pl daemon in the Linux terminal, open Chrome on your Chromebook and go to http://localhost:3000. The web app runs on the same machine and the connection is immediate — no network involved.HTTP Client Scripts
Perl's LWP::UserAgent (or Mojolicious's Mojo::UserAgent) make it easy to fetch web data from your Chromebook scripts.
#!/usr/bin/perl
# =============================================================================
# web_fetch.pl — HTTP client examples using LWP and Mojo
# Install: cpanm LWP::UserAgent Mojolicious
# Run: perl web_fetch.pl
# =============================================================================
use strict;
use warnings;
use LWP::UserAgent;
use JSON::PP;
my $ua = LWP::UserAgent->new(
agent => "PerlChromebook/1.0",
timeout => 10,
);
# ── Fetch plain text ──────────────────────────────────────────────────────────
print "=== Plain text fetch ===\n";
my $res = $ua->get("https://httpbin.org/get");
if ($res->is_success) {
my $data = decode_json($res->content);
printf "Your IP: %s\n", $data->{origin} // "unknown";
printf "User-Agent: %s\n", $data->{headers}{Host} // "";
} else {
print "Error: ", $res->status_line, "\n";
}
# ── Fetch and parse JSON API ──────────────────────────────────────────────────
print "\n=== JSON API ===\n";
$res = $ua->get("https://api.github.com/repos/torvalds/linux");
if ($res->is_success) {
my $repo = decode_json($res->content);
printf "Repo: %s\n", $repo->{full_name} // "";
printf "Stars: %d\n", $repo->{stargazers_count} // 0;
printf "Description: %s\n", $repo->{description} // "";
} else {
print "Error: ", $res->status_line, "\n";
}
# ── Save a file ───────────────────────────────────────────────────────────────
print "\n=== Download file ===\n";
my $url = "https://raw.githubusercontent.com/nicowillis/nicowillis/main/README.md";
my $path = "/tmp/fetched.txt";
$res = $ua->get($url);
if ($res->is_success) {
open(my $fh, ">", $path) or die $!;
print $fh $res->content;
close $fh;
printf "Saved %d bytes to %s\n", -s $path, $path;
} else {
# Graceful fallback if URL doesn't exist
printf "Skipped download (status: %s)\n", $res->status_line;
}
# ── Using Mojo::UserAgent (async-capable) ─────────────────────────────────────
print "\n=== Mojo::UserAgent ===\n";
eval {
require Mojo::UserAgent;
my $mua = Mojo::UserAgent->new;
my $json = $mua->get("https://httpbin.org/uuid")->result->json;
printf "UUID from Mojo::UA: %s\n", $json->{uuid} // "(unavailable)";
};
print "Mojo::UserAgent not installed\n" if $@;Termux — Perl via Android Apps
Termux is a terminal emulator and Linux environment for Android. On Chromebooks that have the Google Play Store but do not support the Linux container (or as a supplement), Termux gives you a functional Perl environment in a terminal — but without GUI support.
Install and Configure Termux
# Install Termux from: https://f-droid.org/packages/com.termux/
# (F-Droid version is maintained; Play Store version is outdated)
# Once inside Termux:
pkg update && pkg upgrade -y
# Install Perl
pkg install perl
# Verify
perl -v # Should show v5.36 or newer
# Install build tools for CPAN modules
pkg install build-essential openssl libcrypt
# Install cpanminus
curl -L https://cpanmin.us | perl - App::cpanminus
# or:
pkg install perl-cpanminus
# Install common modules
cpanm Modern::Perl Try::Tiny JSON::PP LWP::UserAgent
# For web development — Mojolicious works in Termux!
cpanm MojoliciousTermux + Mojolicious Web Server
A Mojolicious app in Termux is accessible from Chrome browser running on the same Chromebook at http://localhost:3000 — a nice workflow for web development without the Linux container.
# Save the minimal web app to ~/app.pl
# (use nano app.pl or termux-open to edit)
perl app.pl daemon # Starts server
# Access from Chrome on the Chromebook:
# http://localhost:3000Useful Termux Extras
# Share files with ChromeOS via Termux's storage
termux-setup-storage # Sets up ~/storage/ pointing to Android shared storage
# Your Chromebook Downloads:
ls ~/storage/downloads/
cp ~/storage/downloads/script.pl ~/
# Use a proper terminal font and color scheme
pkg install termux-styling # Sets colors and fonts
# SSH into your Chromebook's Linux container from Termux:
pkg install openssh
ssh penguin@100.115.92.202 # IP of the Linux container (check ifconfig)Cloud IDEs — Browser-Based Perl
For quick experiments, sharing code, or using a Chromebook with limited storage, browser-based IDEs work well for Perl. These require internet access but need no local installation.
Replit
# 1. Go to https://replit.com and sign in
# 2. Click "+ Create Repl" → choose "Perl" as language
# 3. Write and run Perl in the browser — full CPAN access via replit.nix
# .replit config for Perl:
# language = "perl5"
# run = "perl main.pl"GitHub Codespaces
# Codespaces runs Ubuntu with Perl pre-installed
# 1. Go to github.com → any repo → click "Code" → "Codespaces" → "+"
# 2. In the Codespace terminal:
perl -v
sudo apt install -y cpanminus perl-tk
cpanm Mojolicious
# Configure your .devcontainer/devcontainer.json for Perl:
# {
# "image": "mcr.microsoft.com/devcontainers/base:debian",
# "features": {},
# "postCreateCommand": "sudo apt-get install -y perl cpanminus"
# }Comparison
| Service | Perl Version | CPAN | Offline | Free tier |
|---|---|---|---|---|
| Replit | 5.32+ | Limited | No | Yes |
| GitHub Codespaces | 5.34+ | Full via apt+cpanm | No | 60h/month |
| Gitpod | 5.30+ | Full | No | 50h/month |
| play.perl.org | Latest | Selected modules | No | Yes (online REPL) |
Tips & Best Practices
ChromeOS + Linux Integration
- Right-click any .pl file in the Files app → Open with → Linux apps to run or edit it directly.
- Pin the Terminal to the ChromeOS shelf for one-click access.
- Files in
~/show as Linux files in the ChromeOS Files app — drag and drop works between Linux and ChromeOS. - To increase the Linux disk size: Settings → Advanced → Developers → Linux → Disk size.
- Linux apps can be added to the ChromeOS launcher: right-click the app icon in the shelf → Pin to shelf.
Performance Tips
- Chromebooks with 4 GB RAM: avoid running both the Linux container and many Chrome tabs simultaneously.
- Use
cpanm --notest Module::Nameto skip test suites — much faster module installs. - For large file processing, use
File::Findinstead of globbing, and process files line-by-line instead of slurping. - Suspend/resume the Linux container without losing state: Settings → Advanced → Developers → Linux → Shut down Linux — or just close all Linux windows.
ARM Chromebooks (Snapdragon / MediaTek)
- The container runs aarch64 (ARM64) Linux — all Perl and most CPAN modules are supported.
- If
cpanmfails to compile a module with XS (C) code, trysudo apt install libmodulename-perlfirst for the Debian pre-built version. - Perl/Tk (perl-tk) and libgtk3-perl both have ARM64 Debian packages — they install and run correctly.
Backup Your Perl Environment
# List all manually installed apt packages (save this list)
apt list --manual-installed 2>/dev/null | grep -v "automatic" > ~/installed_packages.txt
# List all cpanm-installed modules
cpanm --list-installed > ~/cpan_modules.txt
# Backup your Perl projects to ChromeOS Downloads
cp -r ~/perl_projects /mnt/chromeos/MyFiles/Downloads/perl_backup_$(date +%Y%m%d)/Quick Cheat Sheet
# Full Perl setup for ChromeOS Linux (run once after enabling Linux):
sudo apt update && sudo apt upgrade -y
sudo apt install -y perl perl-doc cpanminus build-essential \
perl-tk libgtk3-perl libwx-perl libglib-perl wget code
cpanm Mojolicious Modern::Perl Try::Tiny Path::Tiny \
DateTime JSON::PP LWP::UserAgent Text::CSV DBI DBD::SQLiteperl script.pl # Run
perl -c script.pl # Syntax check
perl -d script.pl # Debug
perl -e 'say "hi"' # One-liner (needs Modern::Perl or use feature)
code . # Open VS Code in current directory
reply # Perl REPL
# GUI apps — DISPLAY is set automatically in ChromeOS Terminal
perl tk_notes.pl # Opens as ChromeOS window via XWayland
perl gtk3_sysmon.pl # Same
# Web server
perl app.pl daemon # Start at localhost:3000
perl app.pl daemon -l http://*:8080 # Custom port#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use open ':std', ':encoding(UTF-8)';
use feature qw(say state); # or: use Modern::Perl;
# ... your code ...| Goal | Command |
|---|---|
| Check if Linux enabled | Settings → Advanced → Developers |
| Open Linux terminal | Launcher → Terminal (penguin icon) |
| Open Linux files | Files app → Linux files |
| Share Downloads with Linux | Files → right-click Downloads → Share with Linux |
| Run a GUI Perl app | perl myapp.pl (window appears in ChromeOS) |
| Run web server | perl app.pl daemon → Chrome → localhost:3000 |
| Pin Linux app to shelf | Right-click app in shelf → Pin to shelf |
| Increase Linux disk size | Settings → Developers → Linux → Disk size |