2

I am getting an exception like java.io.IOException: Cannot run program cat /home/talha/* | grep -c TEXT_TO_SEARCH": error=2, No such file or directory while executing the command below despite that there are no issues when I execute the same command through the terminal. I need to execute and return the output of the command below:

cat /home/talha/* | grep -c TEXT_TO_SEARCH

Here is the method used to execute commands using Runtime class:

public static String executeCommand(String command) {

    StringBuffer output = new StringBuffer();

    Process p;
    try {
        p = Runtime.getRuntime().exec(command);
        p.waitFor();
        BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));

        String line = "";
        while ((line = reader.readLine()) != null) {
            output.append(line + "\n");
        }

    } catch (Exception e) {
        e.printStackTrace();
    }

    return output.toString();
}
talha06
  • 6,206
  • 21
  • 92
  • 147
  • specify the full path of the `cat` & `grep` commands. – jiveturkey Jun 12 '17 at 13:42
  • but that command works as it is expected through the terminal (bash) – talha06 Jun 12 '17 at 13:45
  • 1
    Well sure, that's because `$PATH` is defined so it knows to look for `cat` along `/usr/bin:/sbin:/usr/sbin:/usr/local/bin` whatever. Within the context of the Java Runtime, it does not have that environment. – jiveturkey Jun 12 '17 at 13:48
  • 1
    Runtime.exec does not use a shell (like, say, `/bin/bash`); it passes the command directly to the operating system. This means wildcards like `*` and pipes (`|`) will not be understood, since `cat` (like all Unix commands) does not do any parsing of those characters. You need to use something like `p = new ProcessBuilder("bash", "-c", command).start();`, or, if for some bizarre reason you need to stick to using the obsolete Runtime.exec methods, `p = Runtime.getRuntime().exec(new String[] { "bash", "-c", command });`. – VGR Jun 12 '17 at 14:47
  • Could you post your comment as an answer? Then, I'll be able to accept your answer as the solution of the problem. @VGR – talha06 Jun 12 '17 at 21:27

2 Answers2

3

Runtime.exec does not use a shell (like, say, /bin/bash); it passes the command directly to the operating system. This means wildcards like * and pipes (|) will not be understood, since cat (like all Unix commands) does not do any parsing of those characters. You need to use something like

p = new ProcessBuilder("bash", "-c", command).start();

or, if for some bizarre reason you need to stick to using the obsolete Runtime.exec methods:

p = Runtime.getRuntime().exec(new String[] { "bash", "-c", command });

If you are only running that cat/grep command, you should consider abandoning the use of an external process, since Java code can easily traverse a directory, read lines from each file, and match them against a regular expression:

Pattern pattern = Pattern.compile("TEXT_TO_SEARCH");
Charset charset = Charset.defaultCharset();

long count = 0;

try (DirectoryStream<Path> dir =
    Files.newDirectoryStream(Paths.get("/home/talha"))) {

    for (Path file : dir) {
        count += Files.lines(file, charset).filter(pattern.asPredicate()).count();
    }
}

Update: To recursively read all files in a tree, use Files.walk:

try (Stream<Path> tree =
    Files.walk(Paths.get("/home/talha")).filter(Files::isReadable)) {

    Iterator<Path> i = tree.iterator();
    while (i.hasNext()) {
        Path file = i.next();
        try (Stream<String> lines = Files.lines(file, charset)) {
            count += lines.filter(pattern.asPredicate()).count();
        }
    };
}
VGR
  • 40,506
  • 4
  • 48
  • 63
  • How can I **recursively** look up for the files under the root directory? – talha06 Jun 12 '17 at 21:49
  • 1
    Updated answer. Use the Files.walk method instead of a DirectoryStream. – VGR Jun 12 '17 at 22:08
  • Getting this exception: `java.nio.file.FileSystemException: Too many open files` – talha06 Jun 12 '17 at 22:48
  • `Runtime.exec` (and also `ProcessBuilder`) does not run a shell unless you explictly say so, so by default globs, pipes and other redirections (`> >> < << <>` etc) don't work, nor do variable or process substitution or flowcontrol like `& && || if while` etc. But `exec(String)` as opposed to `exec(String[])` _does_ whitespace-tokenize the command, so the code posted should not give the particular exception posted. (PS: a _few_ Unix utilities do their own globbing such as `find` and `rsync`, and even their own piping such as `awk`.) – dave_thompson_085 Jun 12 '17 at 23:00
  • 1
    @talha06 Updated answer again. Auto-closing the Stream returned by Files.lines should promptly close each file and should prevent the “Too many open files” error. – VGR Jun 13 '17 at 02:55
0

$PATH is an environment variable that tells the system where to search for executable programs (it's a list of directories separated by colons). It is usually set in your .bashrc or .cshrc file but this is only loaded when you log in. When Java runs, $PATH is likely not set because the rc file is not executed automatically, so the system can't find programs without specifying exactly where they are. Try using /bin/cat or /usr/bin/cat instead of just cat and see if it works. If it does, $PATH is your problem. You can add $PATH=/bin:/usr/bin to your script or just leave it with the directory name specified (e.g. /bin/cat).

Just because you can execute it in a login session doesn't mean it will work the same when a daemon like your Java program runs. You have to know what's in your .bashrc or .cshrc file and even sometimes how the system file is written (/etc/bashrc) in order to know how to write a script that runs under a daemon. Another consideration is that daemons often run under the context of a different user, and that throws things off, too.

Vercingatorix
  • 1,838
  • 1
  • 13
  • 22