102

I am trying to execute some Linux commands from Java using redirection (>&) and pipes (|). How can Java invoke csh or bash commands?

I tried to use this:

Process p = Runtime.getRuntime().exec("shell command");

But it's not compatible with redirections or pipes.

jww
  • 97,681
  • 90
  • 411
  • 885
Narek
  • 38,779
  • 79
  • 233
  • 389
  • 2
    `cat` and `csh` don’t have anything to do with one another. – Bombe Sep 11 '09 at 13:09
  • 4
    i can understand the question for other commands, but for cat: why the hell don't you just read in the file? – Atmocreations Sep 11 '09 at 13:12
  • 9
    Everyone gets this wrong first time - Java's exec() does not use the underlying system's shell to execute the command (as kts points out). The redirection and piping are features of a real shell and are not available from Java's exec(). – SteveD Sep 11 '09 at 14:53
  • stevendick: Thank you very much, I was getting problems because of redirection and piping! – Narek Sep 13 '09 at 19:19
  • System.exit(0) is not inside conditional checking if process is done, so it will always exit without outputting errors. Never write conditionals without braces, to avoid exactly this sort of mistake. –  May 03 '13 at 16:59
  • This is not an answer to this question. – laalto May 03 '13 at 18:23
  • Check here for Runtime exec Pitfalls: http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html – Rui Marques Jul 24 '14 at 14:45

4 Answers4

107

exec does not execute a command in your shell

try

Process p = Runtime.getRuntime().exec(new String[]{"csh","-c","cat /home/narek/pk.txt"});

instead.

EDIT:: I don't have csh on my system so I used bash instead. The following worked for me

Process p = Runtime.getRuntime().exec(new String[]{"bash","-c","ls /home/XXX"});
KitsuneYMG
  • 12,753
  • 4
  • 37
  • 58
  • @Narek. Sorry about that. I fixed it by removing the extra \" 's Apparently they aren't needed. I don't have csh on my system, but it works with bash. – KitsuneYMG Sep 11 '09 at 15:46
  • 3
    As others has mentioned this should be independent wether you have csh or bash, isn't it? – Narek Sep 17 '09 at 05:33
  • @Narek. It should, but I don;'t know how csh handles arguments. – KitsuneYMG Sep 17 '09 at 12:38
  • @Stephan Actually it does if you remember to escape them correctly =/ – KitsuneYMG Jul 12 '11 at 13:30
  • 1
    Thank you. This worked. Actually I used "sh" instead of "csh". – Farshid Jun 02 '12 at 11:12
  • 8
    **Warning: this solution will very likely run into the typical problem of it hanging because you didn't read its output and error streams.** For example: http://stackoverflow.com/questions/8595748/java-runtime-exec – Evgeni Sergeev Dec 11 '14 at 08:46
32

Use ProcessBuilder to separate commands and arguments instead of spaces. This should work regardless of shell used:

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

public class Test {

    public static void main(final String[] args) throws IOException, InterruptedException {
        //Build command 
        List<String> commands = new ArrayList<String>();
        commands.add("/bin/cat");
        //Add arguments
        commands.add("/home/narek/pk.txt");
        System.out.println(commands);

        //Run macro on target
        ProcessBuilder pb = new ProcessBuilder(commands);
        pb.directory(new File("/home/narek"));
        pb.redirectErrorStream(true);
        Process process = pb.start();

        //Read output
        StringBuilder out = new StringBuilder();
        BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String line = null, previous = null;
        while ((line = br.readLine()) != null)
            if (!line.equals(previous)) {
                previous = line;
                out.append(line).append('\n');
                System.out.println(line);
            }

        //Check result
        if (process.waitFor() == 0) {
            System.out.println("Success!");
            System.exit(0);
        }

        //Abnormal termination: Log command parameters and output and throw ExecutionException
        System.err.println(commands);
        System.err.println(out.toString());
        System.exit(1);
    }
}
Tim
  • 19,793
  • 8
  • 70
  • 95
  • Even after java.util.*; is properly imported I can't get above example to run. – Stephan Kristyn Jul 10 '11 at 00:03
  • @Stephan I've updated the example code above to remove the logging statements and variables that were declared outside the pasted code, and it should now compile & run. Feel free to try and let me know how it works out. – Tim Jul 11 '11 at 10:16
16

Building on @Tim's example to make a self-contained method:

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.ArrayList;

public class Shell {

    /** Returns null if it failed for some reason.
     */
    public static ArrayList<String> command(final String cmdline,
    final String directory) {
        try {
            Process process = 
                new ProcessBuilder(new String[] {"bash", "-c", cmdline})
                    .redirectErrorStream(true)
                    .directory(new File(directory))
                    .start();

            ArrayList<String> output = new ArrayList<String>();
            BufferedReader br = new BufferedReader(
                new InputStreamReader(process.getInputStream()));
            String line = null;
            while ( (line = br.readLine()) != null )
                output.add(line);

            //There should really be a timeout here.
            if (0 != process.waitFor())
                return null;

            return output;

        } catch (Exception e) {
            //Warning: doing this is no good in high quality applications.
            //Instead, present appropriate error messages to the user.
            //But it's perfectly fine for prototyping.

            return null;
        }
    }

    public static void main(String[] args) {
        test("which bash");

        test("find . -type f -printf '%T@\\\\t%p\\\\n' "
            + "| sort -n | cut -f 2- | "
            + "sed -e 's/ /\\\\\\\\ /g' | xargs ls -halt");

    }

    static void test(String cmdline) {
        ArrayList<String> output = command(cmdline, ".");
        if (null == output)
            System.out.println("\n\n\t\tCOMMAND FAILED: " + cmdline);
        else
            for (String line : output)
                System.out.println(line);

    }
}

(The test example is a command that lists all files in a directory and its subdirectories, recursively, in chronological order.)

By the way, if somebody can tell me why I need four and eight backslashes there, instead of two and four, I can learn something. There is one more level of unescaping happening than what I am counting.

Edit: Just tried this same code on Linux, and there it turns out that I need half as many backslashes in the test command! (That is: the expected number of two and four.) Now it's no longer just weird, it's a portability problem.

Community
  • 1
  • 1
Evgeni Sergeev
  • 22,495
  • 17
  • 107
  • 124
  • You should ask your question as new StackOverflow question, not as part of your answer. – vog Sep 14 '16 at 06:34
  • Works with two and four on OSX / Oracle java8. Seems there is a problem with your original java environment (which you do not mention the nature of) – TheMadsen Jul 05 '18 at 17:38
0

You can also use my small library called jaxec:

import com.yegor256.Jaxec;
String stdout = new Jaxec("shell", "command")
    .withHome("/home/me") // run it in this directory
    .withRedirect(false) // don't redirect STDERR to STDOUT
    .withCheck(false) // ignore the exit code (no exception if it's not zero)
    .exec();

Here, Jaxec will not only execute the shell command, but also will send its output to Slf4j log, wait for its completion, redirect stderr to stdout, and make sure an exception is raised if the exit code is not equal to zero. Obviously, behind the scene Jaxec uses Runtime.

yegor256
  • 102,010
  • 123
  • 446
  • 597