Update
It is clarified that the words to be replaced are literal strings from a given list (no need to match [a-zA-Z]) -- then use alternation built with these words.† Further, each of these words need be replaced by an also predefined, given, pattern. Use a hash for that.
I assume that a word must not be surrounded by anything other than possibly a _ or a word boundary, on either side. For that one can use lookarounds
A test program
use warnings;
use strict;
use feature 'say';
my @words_to_replace = qw(one ones thing nothing clean);
my %replacement = map { $_ => 'NEW.'.$_ } @words_to_replace;
my $re_word = join '|', @words_to_replace; # no quotemeta; only [a-zA-Z]
my @test = qw(noone ones_ athing _thing nothing. _nothing_ clean);
for (@test) {
printf "For %-12s: ", "|$_|";
if ( s{ (?<! [^_\W]) ($re_word) (?! [^_\W]) }{$replacement{$1}}x ) {
say "mathced |$1|, now have |$_|";
}
else { say '' }
}
I make up a replacement for each word by appending NEW. to it. Prints as expected.
The lookarounds specify that a word must not be surrounded by anything other than _ or \W (character word boundary). That nasty triple negation there (not anything that is not not-word-boundary character) is a way to also account for a zero-width anchor in a lookaround.
† The alternation built with ("thousands" of) words can be a problem for regex if the obtained pattern is longer than some 32k or so characters. If your lists are indeed so long that $re_word's length exceeds this number, perhaps the most economical way is to break the list up into multiple ones that are small enough, and do the above for each. (Trying to match and replace one word at a time will be much slower.)
The original response (believing that we need match [a-zA-Z] with only possible _ around)
One way is to use POSIX character classes, where [[:alpha:]] matches [a-zA-Z]
It isn't clear to me what a replacement for a generic word is, but once it's given
s/([[:alpha:]]+)/$replacement/;
Another way is to form a pattern just as you like it and use that
my $re_char = qr/[a-zA-Z]/;
s/($re_char+)/$replacement/;
Please clarify how that replacement should work (other than foo-bar language).
If the replacement itself doesn't matter but it need be done only when the matched word is possibly surrounded on either side only by _ then one can use lookarounds to exclude any character other than _
m/(?<! [^_] )( [[:alpha:]]+ ) (?! [^_]) /x;
(Edit— To add word boundary use [^_\W] instead. See the first part)
A test program
use warnings;
use strict;
use feature 'say';
my @words = qw(_before _. after_ _both_ none .ahem nah/);
for (@words) {
printf "%-8s:\t", $_;
if ( m/(?<! [^_] )( [[:alpha:]]+ ) (?! [^_]) /x ) {
say $1;
}
else { say "... no match" }
}
This matches words ([a-zA-Z]) with underscore on each or both sides, or nothing around them, but not the ones with other characters around (. and /).
(Edit— To allow for a word-boundary along with _ use [^_\W]. See the first part)