Proposal: spawn()

One command should be able to easily invoke another.

Let's say that my custom command is called x. It accepts one argument, a directory name.

It always perform the following steps:
1. Display the current directory name. To display the current directory name, x should spawn the pwd command.
2. Change to a given directory. To change to a given directory, summary x spawn the cd command.
3. Display the contents of a directory. To display the contents of a directory, x should spawn the ls command.
4. Return to the original directory. To return to the original directory, x should spawn the cd command.

Proposal: Add a spawn() method to AbstractCommand.

protected void spawn( String[] args );

Inside x.java,
print( "Current Directory is " );
spawn( new String[] { "pwd" } );
spawn( new String[] { "cd", directory } );
spawn( new String[] { "ls", "-l" } );
spawn( new String[] { "cd", original } );

But wait! The spawn method requires standard input, output and error parameters.

protected void spawn( String[] args, InputStream stdin, OutputStream stdout, OutputStream stderr );

Inside x.java,
print( "Current Directory is " );
spawn( new String[] { "pwd" }, stdin, stdout, stderr );
spawn( new String[] { "cd", directory }, stdin, stdout, stderr );
spawn( new String[] { "ls", "-l" }, stdin, stdout, stderr );
spawn( new String[] { "cd", original }, stdin, stdout, stderr );

Executing commands in a command

For this feature the JDK has a solution already, though not supported in JNode yet, the Runtime.exec() methods. I think they could be used for this functionality. I would avoid the usage of the name spawn for such function anyway. The example above would look much better in a scripting language. Writing a new command in Java in that manner doesn't look very good anyway, instead I would recommand either a script, or normal Java code which could probably reuse other commands directly by refrering to the command classes without the help of the shell.

Currently the default shell of JNode doesn't support scripting. For convenience it has only a simple command called 'run' for executing a set of commands which are listed in a file but there are no constructs for any other programing laguage feature.

Here the primordial question is if we really need a new scripting laguage for this or we could support and existing one. The Bjorne Shell implementation could be an answer for this with the added benefit of supporting many existing bash scripts under JNode. Other options are BeanShell, JavaScript, Groovy, JRuby and many other scripting languages based on the JVM which are already quite good for creating scripts. The support for easily running such scripts under JNode is in progress and it will be based on the scripting support of the Java 6 platform.

It is among the goals of the default shell to be small and user freindly and to efficiently serve the usual daily needs of a Java developer in a command line based environment. So, many of the fancy commands which can come up in a thought experiment might be simply outside of its scope.

What about JNode and Runtime.exec()?

In an all-Java operating system, is it even possible to implement Runtime.exec()?

For the sake of discussion, let's say that we have a fully functional implementation of javax.isolate.Isolate. It might be possible to implement the Runtime.exec() method to invoke a command--using the alias mechanism. The Runtime.exec() method depends on the Command API.

When JNode is the operating system, the Runtime.exec() method does not create a process for some other operating system.

Meanwhile, the Runtime.exec() method should throw a "Feature is not implemented" exception.

About Runtime.exec()

I was a little surprised by Runtime.exec(). It does not support internal piping.

Runtime.exec( "grep import | sort" );

It requires a program to create the grep process and pipe the output from the grep process to the sort process.

It support standard input, output and error streams only by setting the properties of a Process. It does not support internal redirection. The API provides a way to accomplish this, but without being able to literally accept such a command.

Runtime.exec( "sort < list.txt > sorted-list.txt" );

Does this make sense?

Honestly, I did not test the code on Linux but imho it does not make much sense anyway. If you have "grep import|sort", this is not a command but something that needs to be interpreted. If you call this in e.g. bash you get what you want, but you get this behaviour by the bash and not by the OS. In Linux you'd need to call something like "bash -c 'grep import|sort'" and there's an equivalent for JNode's default Shell afair.

Your right, obviously

Of course! The process is bash. The command line is just another argument to bash. So, that's how it ought to be done.

Is this a platform-independent approach? Let's say that an application creates a process using "bash", "-c", "grep import|sort" as command line arguments. Would it work on every platform? Would it work on JNode? I believe that we could make it work.

I think it was one of

I think it was one of Stephen's goals to make that work, yes. But as you said, you'd still call it with "bash", "-c", "grep import|sort". I'm not quite sure now, but as I understood your earlier post you had expected "grep import|sort" to work too.

I have created an issue to

I have created an issue to make this work and assigned it to myself.

This capability already (almost) exists

I think a command can almost do this already by creating an instance of CommandLine and passing it as an argument to a CommandShell method. But I guess there is value in providing helper methods in the Command or AbstractCommand API to make it easy to do this. There are even potential "value adds" like making the spawned command a subordinate to the one that spawned it.

Shell scripting

How do we implement shell scripting? A shell reads from standard input.

$ shell < myscript.sh

It reads a line from standard input, passes the line of text to the spawn() method. The spawn() method parses the command line, handles piping and redirection.

 try {
   for (;;) {
     String line = readLine();
     spawn( line );
   }
 }
 catch( Throwable e ) {
 }

Unfortunately, the execute( CommandLine, InputStream, OutputStream, OutputStream ) signature makes this difficult implement, but not impossible.

An alternative plan for scripting

Levente and I discussed this sometime back, and we figured that we need a new form of JNode alias that associates a script file and a script interpreter (and maybe some fixed arguments) with a command (alias) name.

We'd need a "protocol" for passing the various arguments etc to a script interpreter via a synthetic CommandLine. Then we'd need to enhance the CommandInvoker implementations to put everything together.

What does Linux do?

Generally speaking, a command line interpreter detects a shell script. Is it necessary to provide an alias for potentially thousands of shell scripts?

In Linux, a shell works with a command path. A command path is part of the environment and contains a list of directories in the file system.

Let's say that we set PATH=/bin:/usr/bin.

set PATH=/bin:/usr/bin

Without delay, the shell uses the command path.

Type "ant" on the command line and press the Enter key.

1. The shell searches the /bin directory--and does not find a file called "ant".

2. The shell searches the /usr/bin directory--and finds a file called "ant". It reads the first character of the file. If the first character is a hash mark (#), it reads the second character. If the second character is an exclamation point (!), it has found a shell script.

3. The name of the script processor is named on the rest of the line. The shell invokes the script processor passing the name of the script as a parameter.

/bin/bash /usr/bin/ant

JNode does not use a PATH

JNode currently does not use a file system command search path to find commands. This is a significant difference between JNode and UNIX-like systems. A "command name" in JNode is an "alias" which gets mapped to a FQ class name for a command class by the AliasManager. The command classes do not exist in the file system's namespace, which is why a classic UNIX-style PATH would not work. Instead command classes are found on the shell's class path, like any other class.

I have always assumed that JNode's current "alias" approach is what we want to use in the long term. The none of the existing CommandInterpreter classes support the PATH concept, and I cannot readily think of a good way to do this ... assuming we wanted to.

Moving from shell to spawn.

The full complexity of command line interpreter should be moved from a shell into a spawn() method. We simplify a shell. We enable any command to invoke any other command.

protected void spawn( String text );

This signature parses the command line--as if it were typed at a shell prompt. It handles piping, redirection, etc..

The complexity is not in the shell.

The complexity for interpreting command lines is in the implementations of the CommandInterpreter interface.

IIRC, there is already a method on CommandShell that takes a text command line and dispatches it to the current interpreter to be parsed and executed.

BTW, we currently have 3 different CommandInterpreter implementations which varying levels of functionality and maturity. The "DefaultInterpreter" just splits command lines into arguments, the "RedirectingInterpreter" understands "<", ">" and "|", and the "BjorneInterpreter" is intended to be a work-alike for the UNIX bourne shell.