views:

428

answers:

4

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:

+1  A: 

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?

JesperE
@Joachim: thanks for the added links!
JesperE
+2  A: 

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.

McDowell
Adding the jni.jar, this bit of JavaScript did the trick. Thank you! var jna = Packages.com.sun.jna; var clib = jna.NativeLibrary.getInstance(jna.Platform.isWindows() ? "msvcrt" : "c"); var csystem = clib.getFunction("system"); csystem.invoke(["echo Hello, World!"]);http://gist.github.com/181225
Kris Kowal
+1  A: 

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.

Carlos Tasada
+1 for the concurrent consumption
Brian Agnew
A: 

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.

Brian Agnew