How to check existence of a program in the path
Asked Answered
D

10

19

I'm writing a program in scala which call:

Runtime.getRuntime().exec( "svn ..." )

I want to check if "svn" is available from the commandline (ie. it is reachable in the PATH). How can I do this ?

PS: My program is designed to be run on windows

Dieldrin answered 1/6, 2009 at 9:52 Comment(1)
you can use the where command under windows. cmd /c where appExec. Then on the returned trimmed string you can do: if(shellResult!=null && shellResult.endsWith('git-bash.exe')) ..... bla blaEvildoer
F
31

Here's a Java 8 solution:

String exec = <executable name>;
boolean existsInPath = Stream.of(System.getenv("PATH").split(Pattern.quote(File.pathSeparator)))
        .map(Paths::get)
        .anyMatch(path -> Files.exists(path.resolve(exec)));

Replace anyMatch(...) with filter(...).findFirst() to get a fully qualified path.


Here's a cross-platform static method that compares common executable extensions:

import java.io.File;
import java.nio.file.Paths;
import java.util.stream.Stream;

import static java.io.File.pathSeparator;
import static java.nio.file.Files.isExecutable;
import static java.lang.System.getenv;
import static java.util.regex.Pattern.quote;

public static boolean canExecute( final String exe ) {
  final var paths = getenv( "PATH" ).split( quote( pathSeparator ) );
  return Stream.of( paths ).map( Paths::get ).anyMatch(
    path -> {
      final var p = path.resolve( exe );
      var found = false;

      for( final var extension : EXTENSIONS ) {
        if( isExecutable( Path.of( p.toString() + extension ) ) ) {
          found = true;
          break;
        }
      }

      return found;
    }
  );
}

This should address most of the critiques for the first solution. Aside, iterating over the PATHEXT system environment variable would avoid hard-coding extensions, but comes with its own drawbacks.

Flint answered 8/5, 2014 at 10:30 Comment(3)
When I try this and run it inside my IDE (I'm using Eclipse), it cannot find a file that has had its path added to my environment PATH variable. However, when I create a runnable JRE from it and run that from my terminal, it works. So I suspect my PATH from inside my IDE is not the same as my PATH when I open a terminal. How can I get around this? I'd rather not make a runnable Jar every time I want to test the code. Thanks!Joshi
I'm also not getting this to work on Windows. If I set exec = java I get a false result. But if I run where java from cmd I get a hit.Joshi
As you're already using streams: .flatMap(path -> Stream.of(EXTENSIONS).map(ext -> path.resolve(exe + ext)))Titivate
P
14

I'm no scala programmer, but what I would do in any language, is to execute something like 'svn help' just to check the return code (0 or 1) of the exec method... if it fails the svn is not in the path :P

Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("svn help");
int exitVal = proc.exitValue();

By convention, the value 0 indicates normal termination.

Ply answered 1/6, 2009 at 10:0 Comment(3)
I think it would be better to do "which svn" instead of "svn help". It will still give the proper return code as to whether or not svn exists in the path, but on a success you will also get the full-path to the svn executable.Twickenham
"where" is the Windows equivalent of "which"Schnorr
You have to invoke Process#waitFor() before trying to get the exit value. See #25979836Nickolenicks
W
4

Selenium has what looks to be a reasonably complete implementation for Windows/Linux/Mac in the class org.openqa.selenium.os.ExecutableFinder, with public access since Selenium 3.1 (previously only accessible via the deprecated method org.openqa.selenium.os.CommandLine#find). It is ASL 2.0 though.

Note that ExecutableFinder doesn't understand PATHEXT on Windows - it just has a hard-coded set of executable file extensions (.exe, .com, .bat).

Wiretap answered 14/1, 2015 at 5:18 Comment(1)
Thanks @gouessej. Looks like it's not part of a release yet: github.com/SeleniumHQ/selenium/blob/selenium-3.0.1/java/client/…Wiretap
C
4

this code uses "where" command on Windows, and "which" command on other systems, to check if the system knows about the desired program in PATH. If found, the function returns a java.nio.file.Path to the program, and null otherwise.

I tested it with Java 8 on Windows 7 and Linux Mint 17.3.

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.Logger;


public class SimulationUtils
{
    private final static Logger LOGGER = Logger.getLogger(SimulationUtils.class.getName());

    public static Path lookForProgramInPath(String desiredProgram) {
        ProcessBuilder pb = new ProcessBuilder(isWindows() ? "where" : "which", desiredProgram);
        Path foundProgram = null;
        try {
            Process proc = pb.start();
            int errCode = proc.waitFor();
            if (errCode == 0) {
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()))) {
                    foundProgram = Paths.get(reader.readLine());
                }
                LOGGER.info(desiredProgram + " has been found at : " + foundProgram);
            } else {
                LOGGER.warning(desiredProgram + " not in PATH");
            }
        } catch (IOException | InterruptedException ex) {
            LOGGER.warning("Something went wrong while searching for " + desiredProgram);
        }
        return foundProgram;
    }

    private static boolean isWindows() {
        return System.getProperty("os.name").toLowerCase().contains("windows");
    }
}

To use it :

    System.out.println(SimulationUtils.lookForProgramInPath("notepad"));

On my Windows 7 system, it displays :

C:\Windows\System32\notepad.exe

And on linux :

    System.out.println(SimulationUtils.lookForProgramInPath("psql"));

/usr/bin/psql

The advantage of this method is that it should work on any platform and there's no need to parse the PATH environment variable or look at the registry. The desired program is never called, even if found. Finally, there's no need to know the program extension. gnuplot.exe under Windows and gnuplot under Linux would both be found by the same code :

    SimulationUtils.lookForProgramInPath("gnuplot")

Suggestions for improvement are welcome!

Chesterchesterfield answered 28/6, 2016 at 10:43 Comment(4)
This actually has terrible performance. Calling external processes should be avoided as much as possible.Hurwitz
@BullyWiiPlaza: Well, achieving good performance wasn't my goal either. If you have any idea how it could be faster while still working in Windows/Linux/MacOS, feel free to suggest anything. BTW, the result could be cached. Finally, the goal is to call an external process. Any svn call will probably be much slower than where or which call.Chesterchesterfield
Yes, the answer above by Dmitry Ginzburg is really good. where has a relatively big overhead (can be more than 100 - 200ms) and if you call it multiple times it gets out of hands. I just realized this in my code as to why it was noticeably slower.Hurwitz
@BullyWiiPlaza: Dmitry's answer is more concise indeed, and it looks faster. I'd argue that it's less robust though. If it works fine for you, great!Chesterchesterfield
M
2

Concerning the original question I'd also check for existence as FMF suggested.

I'd also like to point out that you'll have to handle at least the output of the process, reading available data so the streams won't be filled to the brim. This would cause the process to block, otherwise.

To do this, retrieve the InputStreams of the process using proc.getInputStream() (for System.out) and proc.getErrorStream() (for System.err) and read available data in different threads.

I just tell you because this is a common pitfall and svn will potentially create quite a bit of output.

Matejka answered 1/6, 2009 at 14:27 Comment(0)
H
2

This is similar to Dmitry Ginzburg's answer but it also addresses the rare case of someone having an invalid path in the PATH environment variable. This would cause an InvalidPathException.

private static final String ENVIRONMENT_VARIABLES_TEXT = System.getenv("PATH");

private static boolean isCommandAvailable(String executableFileName)
{
    String[] environmentVariables = ENVIRONMENT_VARIABLES_TEXT.split(File.pathSeparator);
    for (String environmentVariable : environmentVariables)
    {
        try
        {
            Path environmentVariablePath = Paths.get(environmentVariable);
            if (Files.exists(environmentVariablePath))
            {
                Path resolvedEnvironmentVariableFilePath = environmentVariablePath.resolve(executableFileName);
                if (Files.isExecutable(resolvedEnvironmentVariableFilePath))
                {
                    return true;
                }
            }
        } catch (InvalidPathException exception)
        {
            exception.printStackTrace();
        }
    }

    return false;
}

Overall this might now be the most efficient and robust solution.

Hurwitz answered 28/9, 2019 at 14:8 Comment(0)
D
0

If you have cygwin installed you could first call "which svn", which will return the absolute path to svn if it's in the executable path, or else "which: no svn in (...)". The call to "which" will return an exitValue of 1 if not found, or 0 if it is found. You can check this error code in the manner FMF details.

Dominik answered 1/6, 2009 at 16:26 Comment(0)
R
0

In my experience it is impossible to tell over the various systems through just calling a the command with the ProcessBuilder if it exits or not (neither Exceptions nor return values seem consistent)

So here is a Java7 solution that traverses the PATH environment variable and looks for a matching tool. Will check all files if directory. The matchesExecutable must be the name of the tool ignoring extension and case.

public static File checkAndGetFromPATHEnvVar(final String matchesExecutable) {
    String[] pathParts = System.getenv("PATH").split(File.pathSeparator);
    for (String pathPart : pathParts) {
        File pathFile = new File(pathPart);

        if (pathFile.isFile() && pathFile.getName().toLowerCase().contains(matchesExecutable)) {
            return pathFile;
        } else if (pathFile.isDirectory()) {
            File[] matchedFiles = pathFile.listFiles(new FileFilter() {
                @Override
                public boolean accept(File pathname) {
                    return FileUtil.getFileNameWithoutExtension(pathname).toLowerCase().equals(matchesExecutable);
                }
            });

            if (matchedFiles != null) {
                for (File matchedFile : matchedFiles) {
                    if (FileUtil.canRunCmd(new String[]{matchedFile.getAbsolutePath()})) {
                        return matchedFile;
                    }
                }
            }
        }
    }
    return null;
}

Here are the helper:

public static String getFileNameWithoutExtension(File file) {
        String fileName = file.getName();
        int pos = fileName.lastIndexOf(".");
        if (pos > 0) {
            fileName = fileName.substring(0, pos);
        }
        return fileName;
}

public static boolean canRunCmd(String[] cmd) {
        try {
            ProcessBuilder pb = new ProcessBuilder(cmd);
            pb.redirectErrorStream(true);
            Process process = pb.start();
            try (BufferedReader inStreamReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                while ((inStreamReader.readLine()) != null) {
                }
            }
            process.waitFor();
        } catch (Exception e) {
            return false;
        }
        return true;
}
Reinaldo answered 1/11, 2016 at 23:15 Comment(0)
E
0

you can use the where command under windows. Let's suppose you check if the git-bash.exe app is on the windows path. You must run the shell command: cmd /c where git-bash. Then on the returned trimmed string you can do: if(shellResult!=null && shellResult.endsWith('git-bash.exe')) ..... do yout things

Evildoer answered 25/6, 2021 at 11:22 Comment(0)
B
0

Beware: The accepted answer executes the target program. So, if you would use "shutdown", for instance, your system may shutdown just because you wanted to know if it exists.

I added my solution that does an exact match. It does not check if the file is executable. It is case-sensitive. If this does not meet your needs, feel free to adapt it accordingly.

public static File findInPATH(String executable) {
  String[] PATH = System.getenv("PATH").split(File.pathSeparator);
  for (var p : PATH) {
     File entry = new File(p);
     if (!entry.exists())
        continue;

     if (entry.isFile() && entry.getName().equals(executable)) {
        return entry;
     } else if (entry.isDirectory()) {
        for (var f : entry.listFiles())
           if (f.getName().equals(executable))
              return f;
     }
  }
  return null;

}

Burgenland answered 21/5, 2022 at 8:58 Comment(1)
The "accepted answer" seems to have changed after your posting. Avoid using this phrasing, as it tends to make thing illegible afterwards. As of today, @Dimitry's answer is the accepted one, and doesn't look like executing the target program (which indeed wasn't wanted).Tern

© 2022 - 2024 — McMap. All rights reserved.