Do Operator
This page walks through Perl'sdo operator and shows how it handles file loading, inline blocks, conditional execution, and loops.
Part 1: LOADING SUBROUTINES FROM FILES
The most common use ofdo is loading Perl code from external files. Unlike require or use, do skips the loaded-file cache and runs the file every time.
Create a file called 'utils.pl':
Now load and use it:# utils.pl sub greet { my ($name) = @_; return "Hello, $name!"; } sub add { my ($a, $b) = @_; return $a + $b; } 1; # Important! Files must end with a true value
The trailingdo "./utils.pl" or die "Cannot load utils.pl: $!"; print greet("World"), "\n"; # Hello, World! print add(5, 3), "\n"; # 8
1; in the file matters because do returns the value of the last expression. If that value is false (like 0, "", or undef), your or die check would trigger even though the file loaded successfully.
A safer error handling pattern looks like this:
my $result = do "./config.pl"; if ($@) { die "Syntax error in config.pl: $@"; } elsif (!defined $result) { die "Could not read config.pl: $!"; } elsif (!$result) { die "config.pl did not return a true value"; }
Part 2: LOADING ANONYMOUS FUNCTIONS
Here is a useful pattern: files that return anonymous subroutine references. This lets you load behavior dynamically.Create 'formatter.pl':
Notice that no trailing# formatter.pl - Returns an anonymous sub sub { my ($text) = @_; return uc($text) . "!!!"; }
1; is needed here. The anonymous sub itself is the return value, and a code reference is always true.
Load and use it:
This pattern works well for plugin systems:my $format = do "./formatter.pl" or die $!; print $format->("hello"); # HELLO!!!
# Load all formatters from a directory my %formatters; for my $file (glob("./formatters/*.pl")) { my ($name) = $file =~ /(\w+)\.pl$/; $formatters{$name} = do $file or die "Error loading $file: $!"; } # Use them print $formatters{uppercase}->("test"); print $formatters{reverse}->("test");
Part 3: THE do {} BLOCK
do followed by a block executes the block and returns its last expression. This is useful for inline computations and creating lexical scopes.
Basic usage:
This is great for complex initializations:my $result = do { my $temp = 5; $temp * $temp; }; print $result; # 25 # $temp is not accessible here - it was lexically scoped
my $config = do { my %conf; $conf{debug} = $ENV{DEBUG} // 0; $conf{port} = $ENV{PORT} // 8080; $conf{host} = $ENV{HOST} // "localhost"; $conf{url} = "http://$conf{host}:$conf{port}"; \%conf; }; print $config->{url}; # http://localhost:8080
Part 4: CONDITIONAL EXECUTION WITH do-if
You can combinedo with if for conditional block execution:
This is cleaner than nested ternaries for complex conditions, and the result can be assigned directly.my $message = do { if ($age < 13) { "child"; } elsif ($age < 20) { "teenager"; } else { "adult"; } };
Another pattern handles optional file loading:
This loads the config file only if it exists, without dying if it is missing.do "./local_config.pl" if -e "./local_config.pl";
Part 5: LOOPS WITH do-while AND do-until
Unlike regular while loops, do-while executes at least once:This guarantees the block runs before checking the condition. Compare to a regular while:my $count = 0; do { print "Count: $count\n"; $count++; } while ($count < 5);
The do-until variant is also available:my $count = 10; # This prints nothing - condition checked first while ($count < 5) { print "Count: $count\n"; $count++; } # This prints once - block runs first do { print "Count: $count\n"; $count++; } while ($count < 5);
my $input; do { print "Enter 'quit' to exit: "; $input = <STDIN>; chomp($input); } until ($input eq 'quit');
Part 6: INLINE SUBROUTINES WITH do
You can usedo {} to create inline code that captures surrounding variables. This behaves like immediately-invoked function expressions (IIFE) in JavaScript:
This keeps helper variables (my @numbers = (1, 2, 3, 4, 5); my $stats = do { my $sum = 0; my $count = 0; for my $n (@numbers) { $sum += $n; $count++; } { sum => $sum, count => $count, average => $sum / $count, }; }; print "Average: $stats->{average}\n"; # Average: 3
$sum, $count) from polluting the outer scope while still producing a useful result.
Part 7: COMBINING PATTERNS
These patterns combine for stronger effects:Loading config with defaults:
Conditional function loading:my $config = do { my $loaded = do "./config.pl"; # Merge with defaults { debug => 0, verbose => 0, max_size => 1024, %{ $loaded // {} }, # Override with file settings }; };
my $processor = do { if ($ENV{USE_FAST}) { do "./fast_processor.pl"; } else { do "./standard_processor.pl"; } } or die "Cannot load processor: $!"; $processor->($data);
Part 8: KEY POINTS TO REMEMBER
Files loaded withdo:
Anonymous function files:- Must end with 1; (or any true value) - Unless they return an anonymous sub (which is true) - Are executed every time (unlike require) - Don't update %INC
- Don't need trailing 1; - The sub reference itself is the return value - Perfect for plugins and dynamic loading
do {} blocks:
- Create a lexical scope - Return the last expression - Great for complex initializations
do-while/until loops:
- Execute at least once - Condition checked after each iteration
Conclusion
Thedo operator covers several distinct jobs in Perl:
These patterns keep your code cleaner and more flexible. The operator is not flashy, but it is useful once you know how each form behaves.do $file - Execute and return file contents do { ... } - Execute block, return last value do { } while () - Loop at least once do { } if () - Conditional block execution
perl.gg