emacs -batch can do that:
echo '(progn (find-file "123.txt") (replace-string "2" "0") (save-buffer))' | emacs -batch --eval "(call-interactively #'eval-expression)"
This starts emacs in batch mode where it evaluates its command-line arguments and exits. In this case, the only command line argument is --eval which is asked to call eval-expression interactively. Normally eval-expression reads from the minibuffer, but in batch mode there is no minibuffer and eval-expression reads from stdin. The pipe has arranged for Emacs's stdin to be connected with the stdout of echo, so eval-expression ends up being called with the echo output as its argument. It proceeds to open the file, replace the string and save the buffer. Emacs then exits.
Equivalently but a bit closer in spirit to your example:
echo '(progn (replace-string "2" "0") (save-buffer))' | emacs -batch --file 123.txt --eval "(call-interactively #'eval-expression)"
Here Emacs gets two arguments, a --file argument that visits the file and an --eval argument as before.