How do I exec thee? Let me count the ways...

Published , updated

One of the strengths of Tcl is the ease of integration with other software, whether they be COM components, libraries or even executable programs that are not designed for interaction with other programs. Here we look the facilities Tcl offers related to the last of these -- running external programs and optionally interacting with them using standard I/O mechanisms.

The intent here is to compare at a high level the various commands Tcl offers for executing programs and highlight the distinguishing features of each. It is not to explain their use and working so I will not go into the syntax and details of each command -- that might be the subject of later articles.

We start off with the exec and open commands which are part of the Tcl language and portable to all platforms.

The exec command

The exec command starts a pipeline of one or more processes where the standard output, and optionally standard error, of each process is fed into the standard input of the next process in the pipeline. Normally the command waits for all processes in the pipeline to terminate and returns as its result the standard output of the last process in the pipeline. However, if the last argument to exec is &, the command returns immediately and the return value is a list containing the process ids of each process.

A simple example to list open connections using the Windows netstat and findstr programs:

% exec netstat -n | findstr ESTABLISHED

Alternatively, if we wanted to have it just save the output to a file and not wait

% exec netstat -n | findstr ESTABLISHED > connections.log &

Although the exec command is flexible in that it provides for multi-process pipelines, synchronous and background operation, per-process redirection of standard channels etc., there are some common situations where it is inconvenient or unsuitable.

The first situation arises when the executed program either produces a lot of data or produces it in incremental, continuous fashion. Because exec returns all output from the child process in one shot after it terminates, such programs can present a problem.

For example, the netstat command on Windows can be run to produce a list of network connections on a continuous basis by specifying a time interval.

% exec netstat -n 2

If you run this command, you will find it never returns because the exec continues to wait for netstat to complete which it never does. A similar issue arises with respect to passing data to the executed program. Various standard channel redirections can work around this limitation to some extent but do not provide a complete solution.

The open provides a more suitable alternative.

The open command

Like exec, the open command is a part of the core Tcl language. Although it is normally used to open a file, it can also be used to create a process. If the first character of the file name passed to open is |, the remaining characters in the file name are treated similar to the arguments for `exec. The command then returns a channel connected to the child process' standard input and output. The channel can then be read from or written to in incremental fashion.

% set fd [open "|netstat -n | findstr ESTABLISHED" r]
% while {[gets $fd line] >= 0} { puts $line }
...lines skipped...
% close $fd

You can even choose to set the channel to non-blocking and set event handlers to read and write to the channel, none of which is possible with the exec command.

Although the exec and open commands meet most needs for running external programs, there are some special cases on Windows that cannot be handled with them. The TWAPI extension provides the create_process and shell_execute commands for dealing with these cases.

The twapi::create_process command

The create_process command operates at a lower level than exec and open. It creates only a single process and unlike those commands, any desired pipelines have to be explicitly programmed.

What it does provide is more fine grained control of various aspects of the created process context. Some of the more useful ones include

  • user account context, described in another post
  • window attributes such as colors, position and size, title etc.
  • attached console
  • the environment variable values for the process
  • the desktop and window station in which the process is created
  • ability to create the process in suspended mode
  • setting up security descriptors to control access to the process
  • inheritance of open handles
  • process priority
  • process groups

As an example,

% twapi::create_process c:/windows/system32/netstat.exe -cmdline "-n 2" -newconsole 1 -background red -showwindow maximized

will start netstat in a new maximized console window with a red background.

The twapi::shell_execute command

The final command we describe for running programs is shell_execute. There are several uses for this command that are related to the Windows shell. We only summarize those related to program execution here.

The shell_execute command can be used to run programs similarly to exec or create_process. Like the latter, it cannot be used to run a pipeline of processes.

% twapi::shell_execute -path notepad.exe -params {sample.txt}

In addition, you can run a program indirectly by passing the command the path of a data file. This results in Windows running the program associated with the file type. This is useful when you need to run a "logical" program responsible for handling a file but do not know its path.

% twapi::shell_execute -path sample.doc

The above command will run the program associated with the .doc type, generally Microsoft Word. Something similar (with some limitations) can be done indirectly with exec or create_process as well by running the command shell cmd.exe and passing it the START command with the name of the data file. However, the shell_execute is not restricted to a "default" action on the data file. It can ask the program to take a specific action as well. For example, instead of opening the file, we can run the Word program to print it instead.

% twapi::shell_execute -path sample.doc -verb print

The action may not even be related to a file. For example, you can start the email program to compose a message

% twapi::shell_execute -path mailto:[email protected]

(Note: to be pedantic, this may not actually start a program if it was already running.)

Executing Windows programs in elevated mode

Windows Vista introduced the concept of running programs in elevated administrator mode. This is one task that can only be accomplished with shell_execute and none of the other commands. This is done by specifying runas as the action. For example, to run a command shell in elevated mode

% twapi::shell_execute -path cmd.exe -verb runas

will show the Windows UAC dialog asking permission for running cmd.exe in elevated mode.

In summary

So what command is to be preferred for running programs? A summary of the factors:

On Unix, or the code has to be portable, obviously exec and open are the only choices. Use the former for convenience unless the amount of data produced is large or will be produced in incremental fashion. In that case, use open, optionally with file event handlers.

On Windows, use the platform-specific commands when

  • you need to run the process in elevated mode. Here you have to use shell_execute.

  • you need to run under a different user context. Here you have to use create_process.

  • running programs indirectly depending on data associations. Here shell_execute is not mandated but is the most convenient.

  • you need to control the process environment, runtime, security and other settings. In this case create_process is the most flexible.

Otherwise, even on Windows, you are better off sticking to exec or open, certainly for pipelines but also for potential future portability if nothing else.