First the easy part: reading files line by line from the shell is slow, you probably want to use tr instead. See man tr for details.
Second, you need a temporary file for that. You can't read and write a file at the same time (not from the shell anyway). So you need to do something like this:
tr -- "$1" "$2" <"$file" >"$file".tmp
mv -f -- "$file".tmp "$file"
An obvious problem with this is what happens if tr fails, for whatever reason (say because $1 is empty). Then $file.tmp will still get created (it's created when the line is parsed by the shell), then $file gets replaced by it.
So a somewhat safer way to do it would look like this:
tr -- "$1" "$2" <"$file" >"$file".tmp && \
mv -f -- "$file".tmp "$file"
Now $file is replaced only when tr succeeds.
But what if there is another file named $file.tmp around? Well, it will get overwritten. This is where it gets more complicated: the technically correct way to do it is something like this:
tmp="$( mktemp -t "${0##*/}"_"$$"_.XXXXXXXX )" && \
trap 'rm -f "$tmp"' EXIT HUP INT QUIT TERM || exit 1
tr -- "$1" "$2" <"$file" >"$tmp" && \
cat -- "$tmp" >"$file" && \
rm -f -- "$tmp"
Here mktemp creates a temporary file; trap makes sure this file is cleaned up if the script exits (normally or abnormally); and cat -- "$tmp" >"$file" is used instead of mv to make sure permissions of $file are preserved. This can still fail if, say, you don't have write permissions on $file, but the temporary file is removed eventually.
Alternatively, GNU sed has an option to edit files in place, so you could do something like this:
sed -i "s/$1/$2/g" "$file"
However, this is not as simple as it looks either. The above will fail if $1 or $2 have a special meaning for sed. So you need to escape them first:
in=$( printf '%s\n' "$1" | sed 's:[][\/.^$*]:\\&:g' )
out=$( printf '%s\n' "$2" | sed 's:[\/&]:\\&:g;$!s/$/\\/' )
sed -i "s/$in/$out/" "$file"