raku.gg / snippets

File Processing Patterns

2026-04-10

Raku has excellent built-in support for file I/O. This post covers the most common file handling patterns you will need, from reading and writing to searching and transforming file content.

Reading Files

Slurp the Entire File

my $content = "config.txt".IO.slurp; say $content;

Read Lines into an Array

my @lines = "data.txt".IO.lines; say "File has {+@lines} lines"; say @lines[0]; # First line

Lazy Line Processing

For large files, .lines is lazy by default:
for "huge-file.txt".IO.lines -> $line { # Each line is read on demand, not all at once say $line if $line.contains("ERROR"); }

Read Binary Data

my $bytes = "image.png".IO.slurp(:bin); say "File size: {$bytes.elems} bytes"; say "First 4 bytes: {$bytes[^4].map(*.fmt('%02x')).join(' ')}";

Writing Files

Write a String

"output.txt".IO.spurt("Hello, World!\n");

Append to a File

"log.txt".IO.spurt("New entry\n", :append);

Write Lines

my @data = "line one", "line two", "line three"; "output.txt".IO.spurt(@data.join("\n") ~ "\n");

Write with a File Handle

For more control, use an explicit file handle:
my $fh = open "output.txt", :w; $fh.say("First line"); $fh.say("Second line"); $fh.print("No newline here"); $fh.close;

Append with a File Handle

my $fh = open "log.txt", :a; $fh.say("{DateTime.now} - Event occurred"); $fh.close;

File Information

Check If a File Exists

if "config.txt".IO.e { say "File exists"; } say "config.txt".IO.f; # True if it is a regular file say "mydir".IO.d; # True if it is a directory say "script.sh".IO.x; # True if it is executable

File Metadata

my $file = "data.txt".IO; say "Size: {$file.s} bytes"; say "Modified: {$file.modified}"; say "Accessed: {$file.accessed}"; say "Is readable: {$file.r}"; say "Is writable: {$file.w}";

Directory Operations

List Directory Contents

for dir(".") -> $entry { my $type = $entry.d ?? "DIR " !! "FILE"; say "$type {$entry.basename}"; }

Filter by Extension

my @txt-files = dir(".", test => /\.txt$/); say "Found {+@txt-files} text files";

Recursive Directory Walk

sub walk(IO::Path $dir, Int :$depth = 0) { for $dir.dir.sort -> $entry { say "{' ' x $depth}{$entry.basename}"; walk($entry, depth => $depth + 1) if $entry.d; } } walk(".".IO);

Create Directories

"new-dir/sub-dir".IO.mkdir; # Creates parent directories too

Common Processing Patterns

Search and Replace in a File

my $file = "config.txt".IO; my $content = $file.slurp; $content = $content.subst("old-value", "new-value", :g); $file.spurt($content);

Process Lines with Line Numbers

for "source.txt".IO.lines.kv -> $num, $line { say "{$num + 1}: $line"; }

Filter Lines to a New File

my @filtered = "input.txt".IO.lines.grep(/\S/); # Non-blank lines "output.txt".IO.spurt(@filtered.join("\n") ~ "\n");

Transform Each Line

my @transformed = "data.txt".IO.lines.map(*.uc); "upper.txt".IO.spurt(@transformed.join("\n") ~ "\n");

Head and Tail

# First 10 lines say $_ for "file.txt".IO.lines.head(10); # Last 10 lines say $_ for "file.txt".IO.lines.tail(10);

Temp Files

my $tmpfile = $*TMPDIR.child("raku-{$*PID}-{now.Int}.tmp"); $tmpfile.spurt("temporary data"); say "Temp file: $tmpfile"; # Clean up when done $tmpfile.unlink;

CSV-Like Processing

# Read a CSV file into a list of hashes sub read-csv(IO::Path $file --> List) { my @lines = $file.lines; my @headers = @lines.shift.split(','); @lines.map(-> $line { my @values = $line.split(','); my %row; for @headers.kv -> $i, $h { %row{$h} = @values[$i] // ''; } %row }).list } my @records = read-csv("users.csv".IO); for @records -> %r { say "{%r<name>}: {%r<email>}"; }

Log File Patterns

Tail-Follow Simulation

sub tail-follow(IO::Path $file) { my $pos = $file.s; # Start at end of file loop { if $file.s >; $pos { my $fh = $file.open; $fh.seek($pos); while my $line = $fh.get { say $line; } $pos = $fh.tell; $fh.close; } sleep 0.5; } } # Usage: tail-follow("app.log".IO);

Log Rotation

sub rotate-log(Str $path, Int :$keep = 5) { for ($keep...1) -> $n { my $old = "{$path}.{$n - 1}".IO; my $new = "{$path}.{$n}".IO; $old.rename($new) if $old.e; } $path.IO.rename("{$path}.0") if $path.IO.e; } # rotate-log("/var/log/myapp.log");

Comparing Files

sub files-identical(IO::Path $a, IO::Path $b --> Bool) { return False unless $a.s == $b.s; $a.slurp(:bin) eq $b.slurp(:bin) } say files-identical("file1.txt".IO, "file2.txt".IO);

Safe File Writing

Write to a temp file first, then rename (atomic on most filesystems):
sub safe-write(IO::Path $target, Str $content) { my $tmp = $target.sibling($target.basename ~ ".tmp"); $tmp.spurt($content); $tmp.rename($target); } safe-write("important.conf".IO, "key=value\n");

Batch File Processing

Process all matching files in a directory:
for dir(".", test => /\.log$/) -> $file { my $errors = $file.lines.grep(/ERROR/).elems; say "{$file.basename}: $errors errors" if $errors >; 0; }
These patterns cover the vast majority of file handling tasks you will encounter. Raku's IO::Path class and lazy line processing make file operations clean and memory-efficient.