Perl Part I · Module 15 · Command-Line Perl and Script Execution
-i: In-Place Editing
Renaming a signal across four hundred files, correcting a path that moved, updating a header year — these are the jobs where a one-liner earns its keep, because the alternative is opening each file. The -i flag makes Perl write its output back over the file it read, so a substitution becomes an edit. It is also the only flag in this module that can destroy work, and the care it deserves is not where people usually put it: a crash mid-run turns out to be handled well, and the thing that actually loses data is a pattern that was wrong in a way you did not notice until afterwards. This chapter measures both, and ends with a flow that makes a large edit safe to run.
Intermediate11 min readPerlPart ICommand Line
Part I · Module 15 · Chapter 15.9 · -i: In-Place Editing
1. What -i Does
Normally -p prints to standard output. With -i, that output goes back into the file.
use strict;
use warnings;
use File::Temp qw(tempdir);
use File::Spec;
my $dir = tempdir(CLEANUP => 1);
sub fresh {
my ($name) = @_;
open my $w, ">", File::Spec->catfile($dir, $name) or die "cannot write: $!\n";
print $w "clk_a -> u_fifo/CK\nclk_b -> u_alu/CK\nclk_a -> u_regf/CK\n";
close $w;
}
sub show {
my ($name) = @_;
my $p = File::Spec->catfile($dir, $name);
return "(no such file)" unless -e $p;
open my $r, "<", $p or die "cannot read: $!\n";
local $/;
my $c = <$r>;
close $r;
$c =~ s/\n/ | /g;
$c =~ s/ \| \z//;
return $c;
}
# 1. without -i: the file is untouched, the output goes to the terminal
fresh("a.txt");
my $printed = `cd '$dir' && perl -pe 's/clk_a/clk_main/' a.txt`;
$printed =~ s/\s+\z//; $printed =~ s/\n/ | /g;
print "without -i\n";
print " printed : $printed\n";
print " file : ", show("a.txt"), "\n\n";
# 2. with -i.bak: the file is rewritten, the original kept
fresh("b.txt");
system("cd '$dir' && perl -i.bak -pe 's/clk_a/clk_main/' b.txt");
print "with -i.bak\n";
print " file : ", show("b.txt"), "\n";
print " backup : ", show("b.txt.bak"), "\n\n";
# 3. with bare -i: rewritten, nothing kept
fresh("c.txt");
system("cd '$dir' && perl -i -pe 's/clk_a/clk_main/' c.txt");
print "with bare -i\n";
print " file : ", show("c.txt"), "\n";
print " backup : ", show("c.txt.bak"), "\n";without -i
printed : clk_main -> u_fifo/CK | clk_b -> u_alu/CK | clk_main -> u_regf/CK
file : clk_a -> u_fifo/CK | clk_b -> u_alu/CK | clk_a -> u_regf/CK
with -i.bak
file : clk_main -> u_fifo/CK | clk_b -> u_alu/CK | clk_main -> u_regf/CK
backup : clk_a -> u_fifo/CK | clk_b -> u_alu/CK | clk_a -> u_regf/CK
with bare -i
file : clk_main -> u_fifo/CK | clk_b -> u_alu/CK | clk_main -> u_regf/CK
backup : (no such file)Three behaviours, each clear from the output:
Without -i, the altered text went to standard output and the file was untouched. That is -p as Chapter 15.7 described it, and it is how you preview an edit.
With -i.bak, the file now holds the edited text and b.txt.bak holds exactly what was there before.
With bare -i, the file is edited and there is no backup anywhere. The original is gone.
-i works with -n too, where it is how you delete lines:
perl -ni -e 'print unless /^#/' config.txt # strip comment linesWith -n, only what you print is written back — so a line you do not print is removed from the file. That is powerful and worth saying out loud, because perl -ni -e '1' file empties the file completely.
2. The Backup Suffix
use strict;
use warnings;
use File::Temp qw(tempdir);
use File::Spec;
my $dir = tempdir(CLEANUP => 1);
for my $case (["plain.txt", "-i.bak", "suffix appended"],
["star.txt", "-i'old_*'", "a * is replaced by the filename"],
["dirless.txt","-i'bak/*'", "a * with a directory prefix"]) {
my ($name, $flag, $why) = @$case;
open my $w, ">", File::Spec->catfile($dir, $name) or die "cannot write: $!\n";
print $w "clk_a\n";
close $w;
mkdir File::Spec->catdir($dir, "bak");
my $err = `cd '$dir' && perl $flag -pe 's/clk_a/clk_main/' $name 2>&1`;
$err =~ s/\s+\z//;
opendir my $dh, $dir or die "cannot read dir: $!\n";
my @found = sort grep { !/^\.\.?$/ && !/^bak$/ } readdir $dh;
closedir $dh;
opendir my $bh, File::Spec->catdir($dir, "bak") or die "cannot read dir: $!\n";
my @inbak = sort grep { !/^\.\.?$/ } readdir $bh;
closedir $bh;
printf "%-12s %-12s files: %-34s bak/: %s\n",
$name, $flag, join(" ", @found), (@inbak ? join(" ", @inbak) : "(empty)");
printf " %s%s\n", $why, ($err ? " [$err]" : "");
# tidy up for the next case
unlink glob(File::Spec->catfile($dir, "*"));
unlink glob(File::Spec->catfile($dir, "bak", "*"));
}plain.txt -i.bak files: plain.txt plain.txt.bak bak/: (empty)
suffix appended
star.txt -i'old_*' files: old_star.txt star.txt bak/: (empty)
a * is replaced by the filename
dirless.txt -i'bak/*' files: dirless.txt bak/: dirless.txt
a * with a directory prefixThe suffix has one piece of syntax worth knowing:
A plain suffix is appended. -i.bak turns report.txt into report.txt.bak.
A * in the suffix is replaced by the original filename. So -i'old_*' gives old_report.txt, and -i'bak/*' puts the backup in a bak subdirectory under its own name. That is much tidier for a bulk edit — all the originals in one place instead of scattered .bak files through the tree.
Quote it for the shell. * is a glob character, so -i'bak/*' needs the quotes or the shell expands it first — Chapter 15.1's measured point, in a new place.
3. What Happens When It Goes Wrong
The reputation of -i is that an interrupted run leaves a truncated file. Measured on this Perl, that is not what happens.
use strict;
use warnings;
use File::Temp qw(tempdir);
use File::Spec;
my $dir = tempdir(CLEANUP => 1);
my $original = "line1 setup\nline2 hold\nline3 setup\n";
sub fresh {
my ($n) = @_;
open my $w, ">", File::Spec->catfile($dir, $n) or die "cannot write: $!\n";
print $w $original;
close $w;
}
sub content {
my ($n) = @_;
my $p = File::Spec->catfile($dir, $n);
return "(does not exist)" unless -e $p;
open my $r, "<", $p or die "cannot read: $!\n";
local $/;
my $c = <$r>;
close $r;
$c =~ s/\n/|/g;
return $c;
}
printf "perl version in use: %vd\n\n", $^V;
# A: dies part way through, WITH a backup suffix
fresh("a.txt");
my $e = `cd '$dir' && perl -i.bak -pe 'die "boom at line \$.\\n" if \$. == 2; s/setup/XXX/' a.txt 2>&1`;
my $rc = $? >> 8;
$e =~ s/\s+\z//;
print "A. dies at line 2, -i.bak exit $rc ($e)\n";
print " a.txt : ", content("a.txt"), "\n";
print " a.txt.bak : ", content("a.txt.bak"), "\n\n";
# B: dies part way through, with BARE -i
fresh("b.txt");
`cd '$dir' && perl -i -pe 'die "boom\\n" if \$. == 2; s/setup/XXX/' b.txt 2>&1`;
print "B. dies at line 2, bare -i exit ", $? >> 8, "\n";
print " b.txt : ", content("b.txt"), "\n\n";
# C: a CLEAN run, for comparison
fresh("c.txt");
`cd '$dir' && perl -i.bak -pe 's/setup/XXX/' c.txt 2>&1`;
print "C. clean run, -i.bak exit ", $? >> 8, "\n";
print " c.txt : ", content("c.txt"), "\n";
print " c.txt.bak : ", content("c.txt.bak"), "\n\n";
# D: the real danger -- a run that SUCCEEDS with the wrong pattern
fresh("d.txt");
`cd '$dir' && perl -i.bak -pe 's/line/LINE/g' d.txt 2>&1`;
print "D. clean run, WRONG pattern exit ", $? >> 8, "\n";
print " d.txt : ", content("d.txt"), "\n";
print " d.txt.bak : ", content("d.txt.bak"), " <- the only way back\n";perl version in use: 5.34.1
A. dies at line 2, -i.bak exit 25 (boom at line 2)
a.txt : line1 setup|line2 hold|line3 setup|
a.txt.bak : (does not exist)
B. dies at line 2, bare -i exit 25
b.txt : line1 setup|line2 hold|line3 setup|
C. clean run, -i.bak exit 0
c.txt : line1 XXX|line2 hold|line3 XXX|
c.txt.bak : line1 setup|line2 hold|line3 setup|
D. clean run, WRONG pattern exit 0
d.txt : LINE1 setup|LINE2 hold|LINE3 setup|
d.txt.bak : line1 setup|line2 hold|line3 setup| <- the only way backRead rows A and B first, because they are the reassuring ones.
A program that dies part way through left the original completely unchanged. No truncation, no half-edited file — and, with -i.bak, no backup either, because the rename never happened. Modern Perl writes to a temporary file and only puts it in place when the run finishes cleanly.
Note the exit status: 25, not 255. That is Chapter 15.4's measured finding showing up again — die picked up a stray errno rather than using 255.
Row C is the normal case: edited file, backup holding the original.
Row D is the one that matters. The run succeeded. The exit status is 0. Perl did exactly what it was told, and what it was told was wrong — s/line/LINE/g was not the edit that was wanted. There is no crash to protect against here, no atomicity to rely on, and the only way back is d.txt.bak.
4. A Safe Bulk Edit
Putting the module together, this is how to rename a signal across a tree without risking the tree:
Step 1 — see what would match, changing nothing.
grep -rln 'clk_a' --include='*.v' .Step 2 — preview the edit on standard output, with no -i at all:
perl -pe 's/\bclk_a\b/clk_main/g' top.v | diff top.v - | head -40diff against - compares the file with the proposed output. This is the step people skip and the one that catches a wrong pattern before it is written anywhere.
Step 3 — note the word boundaries. \b from Chapter 11.8 is why clk_a does not match inside clk_async. A rename without boundaries is the classic way to corrupt a hundred files at once.
Step 4 — run it with a backup, on one file, and check:
perl -i.bak -pe 's/\bclk_a\b/clk_main/g' top.v
diff top.v.bak top.vStep 5 — then the rest, backups into one directory:
mkdir -p .bak
perl -i'.bak/*' -pe 's/\bclk_a\b/clk_main/g' $(grep -rln 'clk_a' --include='*.v' .)Step 6 — verify, then clean up. Re-run the grep from step 1: it should find nothing. Then remove .bak when you are satisfied, not before.
Two closing notes:
Use version control if you have it. git diff after the edit is better than every step above, and git checkout is a better undo than any backup file. The flow here is for the trees that are not in version control, which in this field is more of them than anyone would like.
-i on a file you do not own fails, and it fails per-file in the middle of a list. Chapter 15.3's point applies: check writability first if you are editing a hundred files, or you will discover file sixty-one is read-only after sixty have changed.
5. What To Remember
-iwrites-p's output back into the file instead of to the terminal.- Without
-ithe file is untouched — that is how you preview an edit. -i.bakkeeps the original; bare-ikeeps nothing.- With
-nand-i, a line you do not print is deleted — andperl -ni -e '1' fileempties it entirely. - A
*in the suffix is replaced by the filename, so-i'bak/*'collects originals in one directory. - Quote the suffix —
*is a glob character the shell would expand first. - Measured: a program that dies mid-file leaves the original unchanged and creates no backup. Perl renames only on success.
- That exit status was 25, not 255 — Chapter 15.4's errno leak again.
- Do not drop the suffix because of that. Atomicity is version-dependent and only covers crashes.
- The real danger is a successful run of a wrong pattern — exit 0, file rewritten, backup the only way back.
- Preview with
diff file -before writing anything. - Use
\bword boundaries in a rename, orclk_amatches insideclk_async. - Run on one file first, then the rest.
- Version control beats every backup scheme where it is available.
- Check writability before a bulk edit, or file sixty-one fails after sixty succeeded.
Chapter 15.10 leaves one-liners behind and returns to scripts. Launching another program is something nearly every tool does, and there are three ways to do it in Perl that are not interchangeable — one gets you the status, one gets you the output, and one does not come back at all.
Where this fits
Part of the Perl curriculum.
