Perform localized, evaluation-leak free replacements
Here's a way making use of Block
to cause f
to be inert:
Block[{f},
SetAttributes[f, HoldAllComplete];
expr /. f[args__] :>
RuleCondition[f[args] /. Print -> Echo]
]
Hold[{f[{1, {Echo[1]}}], g[{{{Print[1]}}}]}]
Note of course that this only works if you have a pattern of the form _Symbol[...]
[Edit: For most situations, @Kuba's answer is better]
I can think of one (ugly) way to do it:
Attributes[myHold] = {HoldAll};
expr /.
f[args__] :> With[
{res = myHold[args] /. Print -> Echo},
f @@ res /; True
] /.
HoldPattern[f_ @@ myHold[args__]] :> f[args]
(* Hold[{f[{1, {Echo[1]}}], g[{{{Print[1]}}}]}] *)
The idea is to wrap the contents of f
inside a function with HoldAll
attribute (not Hold
, to be able to identify it uniquely later on). In a first step, the expression is returned with myHold[…]
still in place. In a second round of replacements, myHold
is stripped out again.
Alternatively:
expr /.
foo_f :> RuleCondition[Hold[foo] /. Print -> Echo] /.
Hold[foo_f] :> foo
We can safely perform the second replacement because we just wrapped every f[..]
with Hold
.