Skip to content

Instantly share code, notes, and snippets.

@daemondevin
Last active March 7, 2026 10:46
Show Gist options
  • Select an option

  • Save daemondevin/a81b369ac0d5b45f97f75f09830a7204 to your computer and use it in GitHub Desktop.

Select an option

Save daemondevin/a81b369ac0d5b45f97f75f09830a7204 to your computer and use it in GitHub Desktop.
PHP CLI text find/replace with support for regex, case sensitivity, recursion, and dry-run preview.

replaceText.php

A PHP CLI utility for finding and replacing text across files in a directory. Supports plain and regex-based replacements, case-insensitive matching, recursive directory scanning, extension filtering, and a dry-run preview mode.


Requirements

  • PHP 7.1+

Usage

php replaceText.php --find=TEXT --replace=TEXT [options]

Required Arguments

Argument Description
--find=TEXT The text (or regex pattern) to search for
--replace=TEXT The replacement text

Options

Option Description
--dir=PATH Directory to scan (defaults to current working directory)
--ext=php,js,txt Comma-separated list of file extensions to process
--regex Treat --find as a regular expression
--ignore-case Case-insensitive matching
--recursive Scan subdirectories recursively
--dry-run Preview changes without modifying any files

Examples

Simple find and replace:

php replaceText.php --find="foo" --replace="bar"

Case-insensitive, recursive, limited to PHP files:

php replaceText.php --find="foo" --replace="bar" --ignore-case --recursive --ext=php

Regex replace with a capture group:

php replaceText.php --find="/hello (\w+)/" --replace="hi $1" --regex

Preview changes before applying them:

php replaceText.php --find="oldDomain.com" --replace="newDomain.com" --dir=./src --dry-run

Regex Transforms

When using --regex, captured groups can be transformed using the ${transform:$N} syntax, where N is the capture group index.

Transform Description Example input → output
upper Uppercase helloHELLO
lower Lowercase HELLOhello
ucfirst Capitalise first letter hello worldHello world
ucwords Capitalise each word hello worldHello World
camel camelCase hello_worldhelloWorld
snake snake_case helloWorldhello_world

Example — convert snake_case function names to camelCase:

php replaceText.php \
  --find="/function ([a-z_]+)\(/" \
  --replace="function ${camel:$1}(" \
  --regex --ext=php --recursive

Output

Each modified file is reported with the number of replacements made:

UPDATED: /var/www/app/config.php (3 replacements)
UPDATED: /var/www/app/index.php (1 replacements)

------ Summary ------
Files scanned: 24
Files changed: 2
Replacements : 4
Mode: LIVE

In --dry-run mode, lines are prefixed with DRY RUN: and no files are written.

<?php
$options = getopt("", [
"find:",
"replace:",
"dir::",
"regex",
"ignore-case",
"recursive",
"dry-run",
"ext::"
]);
if (!isset($options['find'], $options['replace'])) {
echo "Usage:\n";
echo "php replaceText.php --find=TEXT --replace=TEXT [options]\n\n";
echo "--regex Treat find as regex\n";
echo "--ignore-case Case insensitive\n";
echo "--recursive Scan subdirectories\n";
echo "--dry-run Preview only\n";
echo "--dir=PATH Directory (default cwd)\n";
echo "--ext=php,js,txt Only process extensions\n";
exit(1);
}
$find = $options['find'];
$replace = $options['replace'];
$dir = $options['dir'] ?? getcwd();
$isRegex = isset($options['regex']);
$ignoreCase = isset($options['ignore-case']);
$recursive = isset($options['recursive']);
$dryRun = isset($options['dry-run']);
$extFilter = isset($options['ext']) ? explode(",", $options['ext']) : null;
if (!is_dir($dir)) {
echo "Invalid directory: $dir\n";
exit(1);
}
function applyTransforms($replacement, $matches)
{
return preg_replace_callback(
'/\$\{(\w+):\$(\d+)\}/',
function ($m) use ($matches) {
$transform = $m[1];
$group = $matches[$m[2]] ?? '';
switch ($transform) {
case 'upper':
return strtoupper($group);
case 'lower':
return strtolower($group);
case 'ucfirst':
return ucfirst($group);
case 'ucwords':
return ucwords($group);
case 'camel':
return lcfirst(str_replace(
' ',
'',
ucwords(str_replace(['_', '-'], ' ', $group))
));
case 'snake':
$group = preg_replace('/([a-z])([A-Z])/', '$1_$2', $group);
return strtolower($group);
default:
return $group;
}
},
$replacement
);
}
function regexReplaceWithTransforms($pattern, $replacement, $subject, &$count)
{
return preg_replace_callback(
$pattern,
function ($matches) use ($replacement, &$count) {
$count++;
$result = applyTransforms($replacement, $matches);
for ($i = 0; $i < count($matches); $i++) {
$result = str_replace('$' . $i, $matches[$i], $result);
}
return $result;
},
$subject
);
}
$totalFiles = 0;
$changedFiles = 0;
$totalReplacements = 0;
$iterator = $recursive
? new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir))
: new IteratorIterator(new DirectoryIterator($dir));
foreach ($iterator as $file) {
if (!$file->isFile()) continue;
$path = $file->getPathname();
$ext = pathinfo($path, PATHINFO_EXTENSION);
if ($extFilter && !in_array($ext, $extFilter)) continue;
$totalFiles++;
$contents = file_get_contents($path);
if ($contents === false) continue;
$newContents = $contents;
$count = 0;
if ($isRegex) {
$pattern = $find;
if ($ignoreCase && substr($pattern, -1) !== 'i') {
$pattern .= "i";
}
$newContents = regexReplaceWithTransforms($pattern, $replace, $contents, $count);
} else {
if ($ignoreCase) {
$newContents = str_ireplace($find, $replace, $contents, $count);
} else {
$newContents = str_replace($find, $replace, $contents, $count);
}
}
if ($count > 0) {
$totalReplacements += $count;
$changedFiles++;
if ($dryRun) {
echo "DRY RUN: $path ($count replacements)\n";
} else {
file_put_contents($path, $newContents);
echo "UPDATED: $path ($count replacements)\n";
}
}
}
echo "\n------ Summary ------\n";
echo "Files scanned: $totalFiles\n";
echo "Files changed: $changedFiles\n";
echo "Replacements : $totalReplacements\n";
echo "Mode: " . ($dryRun ? "DRY RUN" : "LIVE") . "\n";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment