11

I am trying to accomplish two things:

  1. I am running cygwin on Windows7 to execute my unix shell commands and I need to automate the process by writing a Java app. I already know how to use the windows shell through Java using the 'Process class' and Runtime.getRuntime().exec("cmd /c dir"). I need to be able to do the same with unix commands: i.e.: ls -la and so forth. What should I look into?

  2. Is there a way to remember a shell's state? explanation: when I use: Runtime.getRuntime().exec("cmd /c dir"), I always get a listing of my home directory. If I do Runtime.getRuntime().exec("cmd /c cd <some-folder>") and then do Runtime.getRuntime().exec("cmd /c dir") again, I will still get the listing of my home folder. Is there a way to tell the process to remember its state, like a regular shell would?


It seems that the bash command line proposed by Paŭlo does not work:

C:\cygwin\bin>bash -c ls -la
-la: ls: command not found

I am having trouble figuring out the technicalities.

This is my code:

p = Runtime.getRuntime().exec("C:\\cygwin\\bin\\bash.exe -c ls -la");
reader2 = new BufferedReader(new InputStreamReader(p.getInputStream()));
line = reader2.readLine();

line ends up having a null value.


I added this to my .bash_profile:

#BASH
export BASH_HOME=/cygdrive/c/cygwin
export PATH=$BASH_HOME/bin:$PATH

I added the following as well:

System Properties -> advanced -> Environment variables -> user variebales -> variable: BASH, value: c:\cygwin\bin

Still nothing...

However, if I execute this instead, it works!

p = Runtime.getRuntime().exec("c:\\cygwin\\bin\\ls -la ~/\"Eclipse_Workspace/RenameScript/files copy\"");
Paŭlo Ebermann
  • 73,284
  • 20
  • 146
  • 210
Martin Klosi
  • 3,098
  • 4
  • 32
  • 39

2 Answers2

11

1. Calling unix commands:

You simply need to call your unix shell (e.g. the bash delivered with cygwin) instead of cmd.

bash -c "ls -la"

should do. Of course, if your command is an external program, you could simply call it directly:

ls -la

When starting this from Java, it is best to use the variant which takes a string array, as then you don't have Java let it parse to see where the arguments start and stop:

Process p = 
     Runtime.getRuntime().exec(new String[]{"C:\\cygwin\\bin\\bash.exe",
                                            "-c", "ls -la"},
                               new String[]{"PATH=/cygdrive/c/cygwin/bin"});

The error message in your example (ls: command not found) seems to show that your bash can't find the ls command. Maybe you need to put it into the PATH variable (see above for a way to do this from Java).

Maybe instead of /cygdrive/c/cygwin/bin, the right directory name would be /usr/bin.

(Everything is a bit complicated here by having to bridge between Unix and Windows conventions everywhere.)

The simple ls command can be called like this:

Process p = Runtime.getRuntime().exec(new String[]{"C:\\cygwin\\bin\\ls.exe", "-la"});

2. Invoking multiple commands:

There are basically two ways of invoking multiple commands in one shell:

  • passing them all at once to the shell; or
  • passing them interactively to the shell.

For the first way, simply give multiple commands as argument to the -c option, separated by ; or \n (a newline), like this:

bash -c "cd /bin/ ; ls -la"

or from Java (adapting the example above):

Process p = 
     Runtime.getRuntime().exec(new String[]{"C:\\cygwin\\bin\\bash.exe",
                                            "-c", "cd /bin/; ls -la"},
                               new String[]{"PATH=/cygdrive/c/cygwin/bin"});

Here the shell will parse the command line as, and execute it as a script. If it contains multiple commands, they will all be executed, if the shell does not somehow exit before for some reason (like an exit command). (I'm not sure if the Windows cmd does work in a similar way. Please test and report.)

Instead of passing the bash (or cmd or whatever shell you are using) the commands on the command line, you can pass them via the Process' input stream.

  • A shell started in "input mode" (e.g. one which got neither the -c option nor a shell script file argument) will read input from the stream, and interpret the first line as a command (or several ones).
  • Then it will execute this command. The command itself might read more input from the stream, if it wants.
  • Then the shell will read the next line, interpret it as a command, and execute.
  • (In some cases the shell has to read more than one line, for example for long strings or composed commands like if or loops.)
  • This will go on until either the end of the stream (e.g. stream.close() at your side) or executing an explicit exit command (or some other reasons to exit).

Here would be an example for this:

Process p = Runtime.getRuntime().exec(new String[]{"C:\\cygwin\\bin\\bash.exe", "-s"});
InputStream outStream = p.getInputStream(); // normal output of the shell
InputStream errStream = p.getInputStream(); // error output of the shell
// TODO: start separate threads to read these streams

PrintStream ps = new PrintStream(p.getOutputStream());
ps.println("cd /bin/");
ps.println("ls -la");
ps.println("exit");
ps.close();
Paŭlo Ebermann
  • 73,284
  • 20
  • 146
  • 210
  • Please put the code into your question (there is an edit button), it is not readable as a comment. – Paŭlo Ebermann Jul 19 '11 at 19:19
  • Also, I think you don't need the `mintty`, since you don't want a terminal - instead directly invoke your `bash.exe`. – Paŭlo Ebermann Jul 19 '11 at 19:21
  • original post edited! I am also failing to understand how answering comments works in 'StackOverflow' – Martin Klosi Jul 19 '11 at 19:35
  • My error - the command must be the argument to the `-c` option, and as such be exactly one argument - either quoted (if you use your windows shell to start it) or simply passed separately. I updated the answer. – Paŭlo Ebermann Jul 19 '11 at 19:57
  • u mean edit the .bash_profile and add each individual command that unix has??!! – Martin Klosi Jul 19 '11 at 20:25
  • No, only the `C:\\cygwin\\bin` directory (or its cygwin equivalent) should be in the PATH variable. (I can't really test this, as I'm not using Windows (and Cygwin) for some years now.) – Paŭlo Ebermann Jul 19 '11 at 20:29
  • Uh, no. The question is still in the form it was one hour before. – Paŭlo Ebermann Jul 19 '11 at 21:21
  • gives me this: Oops! Your edit couldn't be submitted because: Your post appears to contain code that is not properly formatted as code. Please indent all code by 4 spaces using the code toolbar button or the CTRL+K keyboard shortcut. For more editing help, click the [?] toolbar icon. tried every possible combination with space and tab. keeps giving me the same problem – Martin Klosi Jul 19 '11 at 21:29
  • You should be able to format code as such by putting four spaces before each line of code. – Paŭlo Ebermann Jul 19 '11 at 21:37
  • If this does not work, try adding some spaces before other lines (maybe this check finds some of the text lines as code). (I'll remove it again.) – Paŭlo Ebermann Jul 19 '11 at 21:39
  • (For the next time, you need an empty line before each code block.) – Paŭlo Ebermann Jul 19 '11 at 21:53
  • Passing the commands to the PrintStream it's not working. Are you sure the syntax is what is supposed to be. I surched for the argument -s and could find what it is. Also shouldn't exec be called from getRuntime() func first? and shouldn't it accept a String array instead of two Strings? – Martin Klosi Jul 20 '11 at 18:04
  • @MartinKlosi let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/1673/discussion-between-palo-ebermann-and-martin-klosi) – Paŭlo Ebermann Jul 20 '11 at 18:23
  • I joined the chat, but I think I am late ... :) – Martin Klosi Jul 20 '11 at 22:17
1

You do not need cygwin here. There are several pure Java libraries implementing SSH protocol. Use them. BTW they will solve your second problem. You will open session and execute command withing the same session, so the shell state will be preserved automatically.

One example would be JSch.

Paŭlo Ebermann
  • 73,284
  • 20
  • 146
  • 210
AlexR
  • 114,158
  • 16
  • 130
  • 208
  • 1
    SSH will not help to execute Unix shell commands on a windows box. – Paŭlo Ebermann Jul 19 '11 at 19:15
  • the jsch's javadocs dont work and i cant figure out the dependencies. could you help me? what goes in my pom.xml file? – Martin Klosi Jul 19 '11 at 19:19
  • @Martin: JSch is not a unix emulation in Java, it is a SSH client implementation. You can use this to connect to a server (usually another computer) which then must have a SSH server and a shell. – Paŭlo Ebermann Jul 19 '11 at 20:12
  • There is some documentation in the [Manual in the JSch wiki](http://sourceforge.net/apps/mediawiki/jsch/index.php?title=Manual). (Written partly by me.) – Paŭlo Ebermann Jul 19 '11 at 20:13
  • can I bypass all this hassle of trying to run unix commands on windows box by using JSch? I AM actually trying to access a different computer through SSH and running unix commands on the remote computer. What I am trying to do is: ssh . Then, get the output this produces on the local machine and write it to a file. – Martin Klosi Jul 19 '11 at 22:22
  • @Martin: Yes, you can, this is just what JSch is for. Have a look at the examples linked in the manual, or on other [questions tagged JSch](http://stackoverflow.com/questions/tagged/jsch) here. If this does not help, open a new question. – Paŭlo Ebermann Jul 19 '11 at 22:36
  • @Martin: On the other hand, you could simply try to use `ssh` (or `c:/cygwin/bin/ssh.exe`) instead of bash for all the examples. – Paŭlo Ebermann Jul 19 '11 at 22:38