Home / Alt manpages / perlfaq5(1)

  • perlfaq5(1)
  • User command
  • linux

Edit Text Files Safely with Perl's Standard Tools

You will finish with small Perl programs for changing lines, counting records, making a copy, and reading a whole file when that is genuinely appropriate. The examples target Perl 5.38.2 from Ubuntu's perl-doc package version 5.38.2-3.2ubuntu0.6. Allow about 15 minutes, plus time to test against a disposable copy of your data.

You need a shell, Perl, and a text file you are allowed to change. Check the interpreter before you begin:

$ perl --version
This is perl 5, version 38, subversion 2 (v5.38.2)

These examples do not need root. Do not run them as root to get around a permissions error. That can turn a small editing mistake into a system-wide one.

1. Make a new file while changing lines

The safest general pattern reads the old file and writes a separate output file. It gives you a chance to inspect the result before replacing anything. This example changes every standalone, case-sensitive perl to Perl:

use strict;
use warnings;

my ($source, $destination) = @ARGV;
die "usage: $0 SOURCE DESTINATION\n" unless defined $destination;

open my $in,  '<', $source
    or die "Cannot read $source: $!\n";
open my $out, '>', $destination
    or die "Cannot write $destination: $!\n";

while (<$in>) {
    s/\bperl\b/Perl/g;
    print {$out} $_ or die "Cannot write $destination: $!\n";
}

close $in  or die "Cannot close $source: $!\n";
close $out or die "Cannot close $destination: $!\n";

Save it as change-case.pl, then run it with an explicit destination:

$ perl change-case.pl notes.txt notes.new
$ diff -u notes.txt notes.new

Checkpoint: the original is still untouched. If the diff is wrong, remove or ignore notes.new and correct the program. If it is right, replace the original only after checking your backup and recovery plan. A simple mv notes.new notes.txt changes the directory entry and may discard the old name, so preserve a copy first when the file matters.

2. Insert, replace, or delete a particular line

Perl's input line counter, $., is useful inside the same loop. This program inserts a line before line three and removes lines beginning with a comment marker. It still writes a new file rather than editing the source in place:

while (<$in>) {
    print {$out} "Added before line three\n" if $. == 3;
    next if /^\s*#/;
    print {$out} $_ or die "write failed: $!\n";
}

For a one-off substitution, Perl's -p switch supplies the loop and prints $_ after the expression:

$ perl -pe 's/old-name/new-name/g' input.txt > output.txt
$ diff -u input.txt output.txt

Use a shell-quoted expression so the shell does not reinterpret Perl's punctuation. If the replacement contains a single quote, use a script file or a different quoting arrangement instead of guessing.

3. Use in-place editing only with a backup

Warning

-i rewrites the named file. A failed expression, an incorrect filename, or a pattern that matches too much can change data immediately. Give -i a suffix so Perl retains the old contents:

$ cp -- notes.txt notes.txt.before-perl
$ perl -pi.perl-old -e 's/alpha/beta/g' -- notes.txt
$ diff -u notes.txt.before-perl notes.txt

The command creates notes.txt.perl-old as well as the manually named copy. The suffix is not a safety guarantee: check that the backup exists and contains the expected old file. To undo this example after verification, restore the deliberate backup:

$ cp -- notes.txt.before-perl notes.txt
$ diff -u notes.txt.before-perl notes.txt

For a single line, test $. in the expression. With -p, Perl prints the current line automatically:

$ perl -pi.perl-old -e 's/old/new/ if $. == 5' -- notes.txt

Remember that $. is the current input line number, not a byte offset. The trailing space in this sentence is not part of the variable name; in code, write $. with no space.

4. Count lines without loading the file

For ordinary newline-delimited text, read one record at a time and use $.:

use strict;
use warnings;

my $file = shift @ARGV // die "usage: $0 FILE\n";
open my $fh, '<', $file or die "Cannot read $file: $!\n";
1 while <$fh>;
print "lines: $.\n";
close $fh or die "Cannot close $file: $!\n";

Run it with:

$ perl count-lines.pl notes.txt
lines: 42

This counts input records using Perl's current input record separator. It is not a universal physical-line counter for files with unusual record endings. If you need to handle a very large file and only count newline bytes, the FAQ also shows a raw sysread loop that processes 4096-byte chunks.

5. Choose between streaming and slurping

Most processing should use while (<$fh>). It keeps memory use bounded by the current record. Loading every line into an array, such as my @lines = <$fh>, is easy to write but scales with the file size.

Read the complete file into one scalar only when the operation needs the complete text, such as a whole-file regular expression:

open my $fh, '<', $file or die "Cannot read $file: $!\n";
my $contents = do { local $/; <$fh> };
close $fh or die "Cannot close $file: $!\n";

$contents =~ s/BEGIN DATA.*?END DATA/BEGIN DATA\nreplacement\nEND DATA/s;
print $contents;

The localised $/ makes the read return the entire file. It does not make a large file cheap: memory usage still grows with the input.

6. Copy files and create temporary files

For a normal copy, use the standard File::Copy module instead of rebuilding copy semantics yourself:

use strict;
use warnings;
use File::Copy qw(copy);

my ($source, $destination) = @ARGV;
copy($source, $destination)
    or die "Copy failed: $!\n";

A temporary filename assembled from the process ID or current time can collide with another process. If you do not need a name, Perl 5.8 and later can create an anonymous temporary file:

open my $tmp, '+>', undef or die "Cannot create temporary file: $!\n";
print {$tmp} "temporary data\n" or die "Cannot write temporary file: $!\n";

When another program must open the file by name, use File::Temp and let it choose a safe location and unique name. Do not put secrets in a predictable filename under a shared directory.

7. Check the result and keep the boundary clear

Perl's file examples can replace text, copy data and remove directory entries, but they do not make a change atomic merely because a new file was used. For important configuration, validate the generated file before activation, keep a known-good copy, and arrange a tested restore path. The FAQ's directory-tree removal example uses File::Path::remove_tree; treat that as destructive and do not use it until the exact directory has been printed and reviewed.

Do not confuse a readable file with a safe file. Check its ownership and permissions before processing sensitive content, and avoid printing secrets while debugging. Ordinary reads and writes need only the permissions of the invoking user. Use elevated privileges only when the target is deliberately root-owned, and make the path and backup explicit before doing so.

Done means

  • You can produce a changed output file while leaving the source intact.
  • You know that -i edits in place and you use a backup suffix before it.
  • You count records with a streaming loop instead of loading a large file by default.
  • You use File::Copy for copies and safe temporary-file facilities for temporary data.
  • You have verified the output and can restore the original before activating a change.