Você só deseja usar o:
find . -name '*.txt' -exec cmd {} \;
formulário para os cmd s que podem receber apenas um argumento. Esse não é o caso de grep . Com grep :
find . -name '*.txt' -exec grep foo /dev/null {} +
(ou use -H com GNU grep ). Mais sobre isso em greping recursivo vs find / -tipo f -exec grep {} \; Qual é mais eficiente / mais rápido?
Agora, para substituição, é o mesmo, perl -pi pode levar mais de um argumento:
find . -name '*.txt' -type f -exec perl -pi -e s/foo/bar/g {} +
Agora, isso seria reescrever os arquivos independentemente de conterem foo ou não. Em vez disso, você pode querer (assumindo o GNU grep e xargs ou compatível):
find . -name '*.txt' -type f -exec grep -l --null foo {} + |
xargs -r0 perl -pi -e s/foo/bar/g
Ou:
grep -lr --null --include='*.txt' foo . |
xargs -r0 perl -pi -e s/foo/bar/g
Portanto, apenas os arquivos que contêm foo serão reescritos.
BTW, --include=*.txt ( --include sendo outra extensão do GNU) é um shell glob, portanto, deve ser citado. Por exemplo, se houvesse um arquivo chamado --include=foo.txt no diretório atual, o shell expandiria --include=*.txt para isso antes de chamar grep . E, se não, com muitos shells, você receberia um erro sobre a falha do glob em corresponder a qualquer arquivo.
Então você quer o grep --include='*.txt'