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];
Lazy Line Processing
For large files, .lines is lazy by default:
for "huge-file.txt".IO.lines -> $line {
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;
say "mydir".IO.d;
say "script.sh".IO.x;
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;
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/);
"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
say $_ for "file.txt".IO.lines.head(10);
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";
$tmpfile.unlink;
CSV-Like Processing
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;
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;
}
}
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;
}
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.