It's also worth noting that some (many?) programs behave differently when run by a user (ie, at the command line) vs being part of a batch process/run from another process. The actual difference is whether there's a terminal attached to the process.
The spawn command, part of the Expect extension to Tcl, sets things up so the executed program sees itself as run by a user, and then adds functionality to allow the tcl program to interact with the external program in a convenient way (ie, matching it's output via regular expressions and branch off those matches). Things run via spawn are run asynchronously.
The exec command is a core Tcl command that runs the other program, returning it's output. It does not do any of the complicated setup that spawn does, but can be very handy for just running a program and seeing it's output (and it's return code). Things run by exec are, by default, run synchronously, but an ampersand at the end of the command causes it to be run in the background (much like traditional shell scripting).
The open command, while generally used to read/write files, can also be used to run external processes. By preceding the command name with the pipe (|) symbol, you tell it to run an external process, and gain access to read/write file descriptors to interact with the resulting process. This is sort of a middle ground between exec and spawn, with much more interaction with the process available, but without the complicated environmental setup that spawn does. It can be extremely handy for interacting with programs that require input but are still fairly well setup for automation.