What's the difference between "env" and "set" (on Mac OS X or Linux)?

Long story short: set can see shell-local variables, env cannot.

Shells can have variables of 2 types: locals, which are only accessible from the current shell, and (exported) environment variables, which are passed on to every executed program.

Since set is a built-in shell command, it also sees shell-local variables (including shell functions). env on the other hand is an independent executable; it only sees the variables that the shell passes to it, or environment variables.

When you type a line like a=1 then a local variable is created (unless it already existed in the environment). Environment variables are created with export a=1


If you want to limit the output of the set command to variables only, you may run it in POSIX mode:

type -a env set
help set
(set -o posix; set) | nl

If you need finer control over listing specific variables, you may use Bash builtins such as declare or compgen, or some other Bash tricks.

man bash | less -p '-A action$'  # info on complete & compgen

# listing names of variables
compgen -A variable | nl       # list names of all shell variables
echo ${!P*}                    # list names of all variables beginning with P

compgen -A export | nl         # list names of exported shell variables
export | nl                    # same, plus always OLDPWD
declare -px | nl               # same

declare -pr                    # list readonly variables

# listing names of functions           
compgen -A function | nl
declare -F | nl
declare -Fx | nl

# show code of specified function
myfunc() { echo 'Hello, world!'; return 0; }
declare -f myfunc  

set is a shell builtin, while env is a program (/usr/bin/env)

set does several things, but by itself it lists the environment variables. It can also set/toggle switches, such as set +x or set -v etc.

env by itself lists the exported environment variables, but can run a program in a modified environment

See man 1 env for more information.