Both ! and % allow you to run shell commands from a Jupyter notebook.
% is provided by the IPython kernel and allows you to run \
! 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 immediayely terminates. %cd foo changes the current directory of the notebook process, which is a lasting effect.