Perl: Find Replace Text in Directory 📜
Here is a Perl script that do find and replace.
# -*- coding: utf-8 -*- # perl =pod File name: xah_find_replace.pl Description: This script does find and replace on a given dir. Features: • Allow multiple Find Replace string pairs. • Find strings can be set to regex or literal. • Filter file by file name regex. • Backup copies of original files is made at a user specified folder that preserves all folder structures of original folder. • A report is generated that indicates which files has been changed, how many changes, and total number of files changed. • Changed FILES retain their own/group/permissions settings. usage: Edit the parameters section then run the script homepage: http://xahlee.info/perl/perl_find_replace_in_dir.html Author: Xah Lee Created: 2000-02 Version: 2025-07-08 =cut use utf8; use strict; use File::Find; use File::Path; use File::Copy; use Data::Dumper; # HHHH------------------------------ # parameters my $inputDirPath = "c:/Users/xah/xx/"; # backup dir path. if no exit, will be created my $backupDirPath = "c:/Users/xah/xx_back-2025-07-08-NSkKH"; my $filenameRegex = qr{\.html$}; # 1 for true, 0 for false my $writeToFile = 1; # find replace string pairs my %findReplaceH = ( "find1" => "rep1", "find2" => "rep2", ); # $useRegexQ has values 1 or 0. If 1, inteprets the pairs in %findReplaceH # to be regex. my $useRegexQ = 0; # in bytes. larger files will be skipped my $fileSizeLimit = 2000 * 1000; # HHHH------------------------------ # globals my $fileSeparator = "==file=sep=M6bcX=====================================\n"; # remove ending slash $inputDirPath =~ s[/$][]; $backupDirPath =~ s[/$][]; $inputDirPath =~ m[/(\w+)$]; my $previousDir = $`; # e.g. '/home/joe' my $lastDir = $1; # e.g. 'public_html' my $backupRoot = $backupDirPath . '/' . $1; # e.g. '/tmp/joe_back/public_html' my $refLargeFiles = []; my $totalFileChangedCount = 0; # HHHH------------------------------ # subroutines # fileFilterQ($fullFilePath) return true if file is desired. sub fileFilterQ ($) { my $fileName = $_[0]; if ( ( -s $fileName ) > $fileSizeLimit ) { push( @$refLargeFiles, $fileName ); return 0; } if ( $fileName =~ $filenameRegex ) { # print "checking: $fileName\n"; return 1; } ## if (-d $fileName) {return 0;}; # directory ## if (not (-T $fileName)) {return 0;}; # not text file return 0; } # go through each file, accumulate a hash. sub doFile { my $currentFile = $File::Find::name; my $currentDir = $File::Find::dir; my $currentFileName = $_; if ( not fileFilterQ($currentFile) ) { return 1; } # Read whole file if ( not( open FILE, "<$currentFile" ) ) { die( "Error opening file: $!" ); } my $wholeFileString; { local $/ = undef; $wholeFileString = <FILE>; }; if ( not( close(FILE) ) ) { die("Error closing file: $!"); } # do the replacement. my $replaceCount = 0; foreach my $key1 ( keys %findReplaceH ) { my $pattern = ( $useRegexQ ? $key1 : quotemeta($key1) ); $replaceCount = $replaceCount + ( $wholeFileString =~ s/$pattern/$findReplaceH{$key1}/g ); } if ( $replaceCount > 0 ) { $totalFileChangedCount++; if ($writeToFile) { # do backup # make a directory in the backup path, make a backup copy. my $pathAdd = $currentDir; $pathAdd =~ s[$inputDirPath][]; mkpath( "$backupRoot/$pathAdd", 0, 0777 ); copy( $currentFile, "$backupRoot/$pathAdd/$currentFileName" ) or die "error: file copying file failed on $currentFile\n$!"; # write to the original # get the file mode. my ( $mode, $uid, $gid ) = ( stat($currentFile) )[ 2, 4, 5 ]; # write out a new file. if ( not( open OUTFILE, ">$currentFile" ) ) { die("Error opening file: $!"); } print OUTFILE $wholeFileString; if ( not( close(OUTFILE) ) ) { die("Error closing file: $!"); } # set the file mode. chmod( $mode, $currentFile ); chown( $uid, $gid, $currentFile ); } # print "-----out77311-------------------------------\n"; print $fileSeparator; print "$replaceCount replacements made at\n"; print "$currentFile\n"; } } # HHHH------------------------------ # main find( \&doFile, $inputDirPath ); print "--------------------------------------------\n\n\n"; print "Total of $totalFileChangedCount files changed.\n"; if ( scalar @$refLargeFiles > 0 ) { print "The following large files are skipped:\n"; print Dumper($refLargeFiles); }
Sample output

I've been using this script from 2000 to 2005.
- i switched to Python in ~2005.
- then switched to elisp in ~2007.
- then switched to golang 2018.
- then mostly back to elisp since about 2020.
Find Replace Scripts
- Golang: Find Text in Directory (grep) 📜
- Golang: Regex Find Replace Text in Directory 📜
- Python: Find Replace Text in Directory 📜
- Python: Regex Find Replace Text in Directory 📜
- Perl: Find Replace Text in Directory 📜
- Emacs: Find Replace Text in Directory
- Emacs: Xah Find Replace (xah-find.el) 📦
- Wolfram: Find Replace Text 📜