I have need for a "system" function call, the same as those in Python, Perl, PHP, Ruby, &c. It will be a component of a JavaScript standard library called Narwhal, when it's run on the Rhino JavaScript engine, which is in turn run on Java.
The trouble is that Java's standard library appears to have abstracted away the ability to spawn a subprocess that shares the parent process's stdio. This means that you can't defer interactivity to the subprocess.
My first crack at this was to implement Python's subprocess.popen. This uses three "pumper" threads to actively copy the parent process's stdio independently (to prevent deadlock). Unfortunately this is giving us two problems. First, the input does not close automatically when the sub-process voluntarily exits. Second, the streams to the child process do not buffer and flush properly.
I'm looking for solutions that would make our require("os").system() command work as one would expect.
The project is at http://narwhaljs.org
Relevant code:
- http://github.com/tlrobinson/narwhal/blob/d147c160f11fdfb7f3c0763acf352b2b0e2713f7/lib/os.js#L10
- http://github.com/tlrobinson/narwhal/blob/d147c160f11fdfb7f3c0763acf352b2b0e2713f7/engines/rhino/lib/os-engine.js#L37
Not sure if this is what you're looking for, but you can invoke the C system
function through the JNA library:
public class System {
public interface C extends Library {
C INSTANCE = (C) Native.loadLibrary(
(Platform.isWindows() ? "msvcrt" : "c"), C.class);
public int system(String format);
}
public static void main(String[] args) {
C.INSTANCE.system("vi");
}
}
Cursory testing worked on Windows, anyhow.
If I'm understanding you properly, you want something like that:
import java.util.*;
import java.io.*;
class StreamGobbler extends Thread
{
InputStream is;
String type;
OutputStream os;
StreamGobbler(InputStream is, String type)
{
this(is, type, null);
}
StreamGobbler(InputStream is, String type, OutputStream redirect)
{
this.is = is;
this.type = type;
this.os = redirect;
}
public void run()
{
try
{
PrintWriter pw = null;
if (os != null)
pw = new PrintWriter(os);
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line=null;
while ( (line = br.readLine()) != null)
{
if (pw != null)
pw.println(line);
System.out.println(type + ">" + line);
}
if (pw != null)
pw.flush();
} catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}
public class GoodWinRedirect
{
public static void main(String args[])
{
if (args.length < 1)
{
System.out.println("USAGE java GoodWinRedirect <outputfile>");
System.exit(1);
}
try
{
FileOutputStream fos = new FileOutputStream(args[0]);
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("java jecho 'Hello World'");
// any error message?
StreamGobbler errorGobbler = new
StreamGobbler(proc.getErrorStream(), "ERROR");
// any output?
StreamGobbler outputGobbler = new
StreamGobbler(proc.getInputStream(), "OUTPUT", fos);
// kick them off
errorGobbler.start();
outputGobbler.start();
// any error???
int exitVal = proc.waitFor();
System.out.println("ExitValue: " + exitVal);
fos.flush();
fos.close();
} catch (Throwable t)
{
t.printStackTrace();
}
}
}
I found this piece of code in: JavaWorld sometime ago when I was looking for a similar solution to wrap system calls to some exe files.
My code has evolved a bit since then, but I think is a good example.
You need to monitor the process exit-status separately, either by polling the exit code or by having a separate thread waiting in the Process.waitFor()
method.
On the matter of buffering and flushing the streams, I don't thing there is an easy solution. There are several Java-classes which do buffering in different forms (BufferedInputStream
, etc). Maybe one of them can help?
It's really important to consume the process standard output and error concurrently. See Carlos Tasada's example code elsewhere here.
If you don't do this your code may (or may not) work depending on the output from your spawned process. As and when that output changes (if your spawned process encounters an error, say) then without the concurrent consumption your process will deadlock. Most of the issues I see on SO related to Process.exec()
are blocking related.