Executing System Commands with ColdFusion

This entry has been updated!

Typically when you want to execute a system command you would use the <cfexecute> tag, but there are some situations when this may not be ideal such as when you want to capture both the "standard output" and "error output" streams of the system command.

Standard Output Stream and Error Output Stream

Many command line programs send output to two data 'streams':
a) Standard output stream
b) Error output stream

When you manually execute a program the command line information sent to either stream is simply displayed on the screen - there is no distinction which stream is being used.

Example

For example, suppose we execute the command to determine the version of java running on your system. You may execute the following command:

java -version

Which on my machine results in:

java version "1.6.0_02"
Java(TM) SE Runtime Environment (build 1.6.0_02-b06)
Java HotSpot(TM) Client VM (build 1.6.0_02-b06, mixed mode, sharing)

The problem with <cfexecute>

Suppose you execute the command above using using:

<cfexecute name="java" arguments="-version" timeout="1" variable="output" />
<cfoutput>#output#</cfoutput>

This produces no output!

What happened? Well, cfexecute can only capture output sent to the standard output stream, but not anything sent to the error output stream.

In this example, the java -version command sends all of its output to the standard error stream (which seems a bit odd, but it came in useful for this example).

So how can we capture both the standard output and the standard error streams?

Using a batch file wrapper

One method of capturing both streams is to put the command inside a batch file and send the two output streams to two files, then read them back in again:

Suppose we have a file javaversion.bat with the content

java -version

Then we run the batch file (on a windows machine) as follows

javaversion.bat 1>stdout.txt 2>errout.txt

This is just the syntax for capturing the standard output (stream 1) and error output (stream 2)

This will result in the creation of two files:
stdout.txt which has no data.
errout.txt which has the version data.

The next step would be to read in the files just created to get the output data.

Using Java

A better method would be to take advantage of Java's underlying stream reading classes.

The basic idea is as follows:

<!--- Define our command --->
<cfset command = "java -version">

<!--- Get Java's runtime object which lets us execute system commands. --->
<cfset runtime = createObject("java", "java.lang.Runtime").getRuntime()>

<!--- Execute the command. --->
<cfset process = runtime.exec(command)>

<!--- Get the standard and error data streams. --->
<cfset standardInput = process.getInputStream()>
<cfset errorInput = process.getErrorStream()>

<!--- Let's start reading the standard input stream first. --->

<!--- Create a Java buffered stream reader, which gives us a readLine() function. --->
<cfset streamReader = createObject("java", "java.io.InputStreamReader").init(standardInput)>
<cfset buffererdReader = createObject("java", "java.io.BufferedReader").init(streamReader)>

<!--- Start reading the input stream. --->
<cfloop condition="true">
   
   <!--- Read one line of data. --->
   <cfset line = buffererdReader.readLine()>
   
   <!--- Note that the buffered reader returns a Java NULL when there is nothing
   left to read. When this gets back to ColdFusion this causes the variable
   it is assigned to to become undefined. So that is how we check if we have
   reached the end of the data. --->

   <cfif not isDefined("line")>
      <cfbreak>
   </cfif>
   
   <!--- Just output the line. --->
   <cfoutput>#line#<br></cfoutput>
   
</cfloop>

<!--- Then create another buffered reader to read the error stream ... --->

We can put this into a component which make the functionality a little easier to use:

Download SystemCommand Component

Example usage:

<cfset command = "java -version">
<cfset syscmd = createObject("component","SystemCommand").init()>
<cfset result = syscmd.execute(command)>
<cfdump var="#result#">

The value returned from the execute() function is a struct with the following fields:

command The original command that was executed.
standardOutput The standard output produced by the command.
errorOutput The error output produced by the command.
executeError A structure that contains error details if the execution failed.

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
21 Sep 2007 08:48AM
David Stockton said:
David Stockton's Gravatar Nice to know I'm not the only one stuck interfacing with certain apps that need to be called from the CLI.

Good article.
27 Oct 2007 12:53AM
Kevan Stannard's Gravatar Unfortunately there is a problem with this method - this may work for some applications but not all.

The standard error stream and standard output streams should not be read sequentially - rather they should be read simultaneously. If they are not read simultaneously then the process may 'block' and appear to hang.

So where to from here?

1) Use the Batch File Wrapper method mentioned above, but this results in a few temp files that need to be cleaned up.

2) Write a bit of Java code to do the work for you and call it from within CF.

3) Possibly do this in CF8. The CFTHREAD tag might allow this to be done. Not sure.
6 Nov 2007 03:20PM
Sami Hoda said:
Sami Hoda's Gravatar Nice work. I'm using this in a project!
7 Nov 2007 02:43AM
Kevan Stannard's Gravatar Hi Sami, great!
13 Dec 2007 08:35AM
Kelly said:
Kelly's Gravatar Very nice and useful method. It solves my problem
Add a comment
(will not be published)
(include http://)