Files and Directories Handling in Perl
Perl has strong support for handling files and directories, which are the bread-and-butter of dealing with the disk and the filesystem. Many modules on CPAN (= the Comprehensive Perl Archive Network) aim to facilitate the task even further.
Table of Contents
- The Basics
- Modules
- Examples
- Reading a file line by line
- Copying a file
- Overwriting a file with text
- Processing the Lines of a File
- Reading an entire UTF-8 file into a big variable
- Appending to a File
- Line count
- Deleting a directory tree
- Prepending to a File (While Slurping)
- Get an array of lines in a file with trailing newlines removed
The Basics
For the basics, read about the open function (on perlopentut or on recommended books or tutorials) as well as other built-ins such as opendir, readdir, closedir, mkdir. There are also many UNIX-oriented file-system-builtins listed on perlfunc which may prove of utility.
Modules
Useful modules for files and directories handling are:
- Path-Tiny - a module that provides a “fast utility for working with file paths” and which despite its name, provides a comprehensive and rich API.
- File-Slurper - a module for fast and easy input and output from files and directories. The interface is procedural and quite Spartan, but is still useful. (Note: Using File-Slurp, and File-Slurp-Tiny which were found to be broken, is no longer recommended.)
- File::Spec - a core module to handle file and directory paths portably.
- File::Basename - a core module to portably extract the basename, the dirname, the suffix and other file paths parsing.
- String-ShellQuote - quote strings for passing through the shell. Also see the list forms of system.
- File::Path - a core module to create or remove directory trees (portably).
- File::Copy - a core module to copy files.
- IO-All - a no longer recommended all-in-one IO package with a lot of syntactic sugar, but quite a few quirks and bugs. Non-core.
Directory Traversal
The built-in module for traversing a directory tree in Perl is File::Find, but it has some severe limitations in interface and use. Some better alternatives are:
File-Find-Object - an object-oriented replacement for File::Find that: 1) can be instantiated 2) has an iterative interface 3) can be interrupted in the middle and 4) can return result objects instead of path names.
File-Next - an alternative with an iterative interface, but incapable of being instantiated.
File-Find-Rule, which is still based on File::Find, and File-Find-Object-Rule provide a more convenient and succinct interface for finding what you want.
Path-Iterator-Rule provides an object oriented and iterative file finder, and there's also Path-Class-Rule and IO-All-Rule. Also of interest is its “See Also” section, which contains links to other implementations, and a comparison of them.
Examples
These are a set of examples for manipulating files and directories using Perl. Each will be shown in several versions including ones using IO-All, ones using core modules, and if relevant ones from the command line.
Reading a file line by line
#!/usr/bin/env perl use strict; use warnings; use Path::Tiny qw/ path /; my $filename_to_read = 'my-file.txt'; my $fh = path($filename_to_read)->openr_utf8; while (my $line = <$fh>) { chomp($line); # Do something with $line. }
#!/usr/bin/env perl use strict; use warnings; use autodie; my $filename_to_read = 'my-file.txt'; open my $fh, '<', $filename_to_read; while (my $line = <$fh>) { chomp($line); # Do something with $line. } close($fh);
Copying a file
#!/usr/bin/env perl use strict; use warnings; use Path::Tiny qw/ path /; my ($source_filename, $dest_filename) = @ARGV; path($source_filename)->copy($dest_filename);
#!/usr/bin/env perl use strict; use warnings; use File::Copy qw(copy); my ($source_filename, $dest_filename) = @ARGV; copy($source_filename, $dest_filename);
Overwriting a file with text
#!/usr/bin/env perl use strict; use warnings; use Path::Tiny qw/ path /; path("output.txt")->spew_utf8("Hello World!\n");
#!/usr/bin/env perl use strict; use warnings; use autodie; open my $out, '>:encoding(utf8)', "output.txt"; print {$out} "Hello World!\n"; close($out);
Processing the Lines of a File
#!/usr/bin/env perl use strict; use warnings; use Path::Tiny qw/ path /; my @filenames = @ARGV; foreach my $fn (@filenames) { path($fn)->edit_lines_utf8(sub { s/\bFrom\b/To/g; }); }
#!/usr/bin/env perl use strict; use warnings; use autodie; use File::Temp ( qw(tempfile) ); my @filenames = @ARGV; foreach my $fn (@filenames) { open my $in, '<', $fn; my ($tempout, $temp_fn) = tempfile(); while (my $line = <$in>) { chomp($line); # Perform the operation here. $line =~ s/\bFrom\b/To/g; print {$tempout} "$new_line\n"; } close($in); close($tempout); rename($temp_fn, $fn); }
$ perl -i.bak -lp -e 's/\bFrom\b/To/g' *.txt
Reading an entire UTF-8 file into a big variable
#!/usr/bin/env perl use strict; use warnings; use Path::Tiny qw/ path /; my $string = path($my_filepath)->slurp_utf8;
#!/usr/bin/env perl use strict; use warnings; use autodie; sub _utf8_slurp { my $filename = shift; open my $in, '<:encoding(utf8)', $filename; local $/; my $contents = <$in>; close($in); return $contents; } my $file_contents = _utf8_slurp($my_filepath);
$ perl -i.bak -ln -0777 -C -ln -e 'Something with $_ here' "$my_utf8_filepath"
#!/usr/bin/env perl use strict; use warnings; use IO::All qw/ io /; my $string = io->file($my_filepath)->utf8->all;
Appending to a File
#!/usr/bin/env perl use strict; use warnings; use Path::Tiny qw/ path /; my $string_to_append = "My new line\n"; path($my_file_path)->append_utf8($string_to_append);
#!/usr/bin/env perl use strict; use warnings; use autodie; my $string_to_append = "My new line\n"; { open my $out, '>>', $my_file_path; print {$out} $string_to_append; close($out); }
Line count
#!/usr/bin/env perl use strict; use warnings; use Path::Tiny qw/ path /; sub count_lines { my ($filename) = @_; my $fh = path($filename)->openr_utf8; my $count = 0; while (my $l = <$fh>) { ++$count; } return $count; }
#!/usr/bin/env perl use strict; use warnings; use autodie; sub count_lines { my ($filename) = @_; open my $fh, '<', $filename; my $count = 0; while (my $l = <$fh>) { ++$count; } close($fh); return $count; }
$ perl -lnE 'END{say "$ARGV has $. lines";}' /path/to/myfile.txt
Deleting a directory tree
#!/usr/bin/env perl use strict; use warnings; use Path::Tiny qw/ path /; path("./path-to-subdir")->remove_tree();
#!/usr/bin/env perl use strict; use warnings; use File::Path qw(rmtree); rmtree(["./path-to-subdir"], 1, 1);
Prepending to a File (While Slurping)
#!/usr/bin/env perl use strict; use warnings; use Path::Tiny qw/ path /; my $filename = 'foo.txt'; my $text_to_prepend = "[Text to Prepend]\n"; path($filename)->edit_utf8(sub { s/\A/$text_to_prepend/; return; });
#!/usr/bin/env perl use strict; use warnings; my $filename = 'foo.txt'; my $text_to_prepend = "[Text to Prepend]\n"; open my $in_fh, '<', $filename or die "Could not open file '$filename' for reading! - $!"; my $contents = do { local $/; <$in_fh>;}; close($in_fh); open my $out_fh, '>', $filename or die "Could not open file '$filename' for writing! - $!"; print {$out_fh} $text_to_prepend . $contents; close($out_fh);
#!/usr/bin/env perl use strict; use warnings; use IO::All qw/ io /; my $filename = 'foo.txt'; my $text_to_prepend = "[Text to Prepend]\n"; my $fh = io->file($filename); $fh->print( $text_to_prepend . $fh->all() );
Get an array of lines in a file with trailing newlines removed
#!/usr/bin/env perl use strict; use warnings; use Path::Tiny qw/ path /; my $filename = 'foo.txt'; my @lines = path($filename)->lines_utf8( { chomp => 1, })
#!/usr/bin/env perl use strict; use warnings; use autodie; sub _lines_utf8 { my $filename =shift; open my $fh, '<:encoding(utf-8)', $filename; my @ret; while(my $l=<$fh>) { chomp$l; push@ret, $l; } close $fh; return \@ret; } my $aref = _lines_utf8('/etc/resolv.conf');