!
calls out to a shell (in a new process), while %
affects the process associated with the notebook (or the notebook itself; many %
commands have no shell counterpart).
!cd foo
, by itself, has no lasting effect, since the process with the changed directory immediately terminates.
%cd foo
changes the current directory of the notebook process, which is a lasting effect.