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
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.
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.
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!
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).
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.
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.
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.
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;
}
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
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;
}
Related
I'm looking for a way to run adb commands directly from a java application. While search on Stack Overflow I found the following solution for running shell commands,
public class Utils {
private static final String[] WIN_RUNTIME = {"cmd.exe", "/C"};
private static final String[] OS_LINUX_RUNTIME = {"/bin/bash", "-l", "-c"};
private Utils() {
}
private static <T> T[] concat(T[] first, T[] second) {
T[] result = Arrays.copyOf(first, first.length + second.length);
System.arraycopy(second, 0, result, first.length, second.length);
return result;
}
public static List<String> runProcess(boolean isWin, String... command) {
System.out.print("command to run: ");
for (String s : command) {
System.out.print(s);
}
System.out.print("\n");
String[] allCommand = null;
try {
if (isWin) {
allCommand = concat(WIN_RUNTIME, command);
} else {
allCommand = concat(OS_LINUX_RUNTIME, command);
}
ProcessBuilder pb = new ProcessBuilder(allCommand);
pb.redirectErrorStream(true);
Process p = pb.start();
p.waitFor();
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
String _temp = null;
List<String> line = new ArrayList<String>();
while ((_temp = in.readLine()) != null) {
//System.out.println("temp line: " + _temp);
line.add(_temp);
}
System.out.println("result after command: " + line);
return line;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
This works perfectly, however I couldn't find a solution to add the adb.exe path into the shell commands so that I can execute adb commands.
My project structure is given below,
I'm trying to append the adb path along with the system default shell path using the following way,
Utils.runProcess(true, "/resources/adb.exe devices");
Is there any way to append the adb.exe path from resources into the shell command?
Use the full path to adb.exe that way you don't need to add it to %PATH%.
eg. If you open cmd and run C:\...\adb.exe devices it will work
Alternatively execute this command in the shell to set your path,
setx path "%path%;C:\..."
Edit: Add the adb.exe in your resources folder in the same package as your calling class. Then load it and write it to another location that you happen to know (or generate a path relative to where your jar is located eg.System.getProperty("user.dir");
)
ClassLoader classLoader = getClass().getClassLoader();
File file = new File(classLoader.getResource("adb.exe").getFile());
// now copy this file to a location you already know eg. C:\...\temp\adb.exe
And then invoke adb.exe with the path that you have
I'm aware how to open an exe program with parameters in Java from finding the answer online. However my specific shortcut is a bit complicated for me to understand.
I'm trying to open a shortcut which has the following target:
C:\Windows\System32\javaw.exe -cp jts.jar;total.2012.jar -Dsun.java2d.noddraw=true -Dswing.boldMetal=false -Dsun.locale.formatasdefault=true -Xmx768M -XX:MaxPermSize=128M jclient/LoginFrame C:\Jts
In my program I've split up the location and what I think are the parameters. However when I run the program I get the error 'Could not create Java Virtual Machine, Program will Exit'. Can someone with a better understanding of whats going on explain what I might be doing wrong or point me in a direction where I can read up?
String location = "C:\\Windows\\System32\\javaw.exe";
String p1="-cp jts.jar;total.2012.jar";
String p2="-Dsun.java2d.noddraw=true";
String p3="-Dswing.boldMetal=false";
String p4="-Dsun.locale.formatasdefault=true";
String p5="-Xmx768M";
String p6="-XX:MaxPermSize=128M";
String p7="jclient/LoginFrame" ;
String p8 = "C:\\Jts";
try {
Process p = new ProcessBuilder(location,p1,p2,p3,p4,p5,p6,p7,p8).start();
} catch (IOException ex) {
Logger.getLogger(Openprogramtest.class.getName()).log(Level.SEVERE, null, ex);
}
Each String you pass to ProcessBuilder is a separate argument (except the first one, which is the command).
Think of it like the args[] which are passed to your main method. Each String would be a separate element in the array.
I suspect that p1 is been interpreted as a single argument, when it should actually be two...
Try separating this argument into two separate parameters
String location = "C:\\Windows\\System32\\javaw.exe";
String p1="-cp";
String p2="jts.jar;total.2012.jar";
String p3="-Dsun.java2d.noddraw=true";
String p4="-Dswing.boldMetal=false";
String p5="-Dsun.locale.formatasdefault=true";
String p6="-Xmx768M";
String p7="-XX:MaxPermSize=128M";
String p8="jclient/LoginFrame" ;
String p9 = "C:\\Jts";
Amendment
Look at the -cp parameter, it appears that the class path elements are relative to the location that the command is executed. This suggests that you need to use the ProcessBuilder#directory(File) to specify the location that the command should executed from.
For example, if you program is installed in C:\Program Files\MyAwesomeApp, but you run it from the context of C:\Desktop, then Java won't be able to find the Jar files it needs, generally raising a ClassNotFound exception.
Instead, you need to tell ProcessBuilder that you want the command to executed from within the C:\Program Files\MyAwesomeApp context.
For example...
ProcessBuilder pb = new ProcessBuilder(...);
pb.directory(new File("C:\Program Files\MyAwesomeApp"));
// Other settings...
Process p = pb.start();
Updated from running example
Just to make the point. I built myself a little Java program that simple printed a simple message to the standard out.
When I run this, it works as expected...
try {
String params[] = new String[]{
"C:\\Windows\\System32\\javaw.exe",
"-cp",
"C:\\...\\TestSimpleProcessBuilder\\build\\classes",
"-Dsun.java2d.noddraw=true",
"-Dswing.boldMetal=false",
"-Dsun.locale.formatasdefault=true",
"-Xmx768M",
"-XX:MaxPermSize=128M",
"testsimpleprocessbuilder/HelloWorld",
"Boo"
};
ProcessBuilder pb = new ProcessBuilder(params);
pb.redirectErrorStream();
Process p = pb.start();
InputStream is = p.getInputStream();
int in = -1;
while ((in = is.read()) != -1) {
System.out.print((char) in);
}
is = p.getErrorStream();
in = -1;
while ((in = is.read()) != -1) {
System.out.print((char) in);
}
System.out.println("p exited with " + p.exitValue());
} catch (IOException ex) {
Logger.getLogger(TestSimpleProcessBuilder.class.getName()).log(Level.SEVERE, null, ex);
}
When I change the arguments from
"-cp",
"C:\\...\\TestSimpleProcessBuilder\\build\\classes",
to
"-cp C:\\...\\TestSimpleProcessBuilder\\build\\classes",
It fails with...
And outputs
Unrecognized option: -cp
C:\DevWork\personal\java\projects\wip\StackOverflow\TestSimpleProcessBuilder\build\classes
And if you're wondering, this is the little test program I wrote that gets run...
package testsimpleprocessbuilder;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello world - world says " + (args.length > 0 ? args[0] : "Nothing"));
}
}
Does anyone know how Java is able to circumvent the windows MAX_PATH limitations. Using the below code I was able to create a really long path in Java and was able to perform I/O, which would have been impossible using windows without prefixing \\?\.
public static void main(String[] args) throws IOException {
BufferedWriter bufWriter = null;
try {
StringBuilder s = new StringBuilder();
for (int i = 0; i < 130; i++) {
s.append("asdf\\");
}
String filePath = "C:\\" + s.toString();;
System.out.println("File Path = " + filePath);
File f = new File(filePath);
f.mkdirs();
f = new File(f, "dummy.txt");
System.out.println("Full path = " + f);
bufWriter = new BufferedWriter(new FileWriter(f));
bufWriter.write("Hello");
}
catch (Exception e) {
e.printStackTrace();
}
finally {
if (bufWriter != null) {
bufWriter.close();
}
}
}
From the JVM's canonicalize_md.c:
/* copy \\?\ or \\?\UNC\ to the front of path*/
WCHAR* getPrefixed(const WCHAR* path, int pathlen) {
[download JVM source code (below) to see implementation]
}
The function getPrefixed is called:
by the function wcanonicalize if ((pathlen = wcslen(path)) > MAX_PATH - 1)
by the function wcanonicalizeWithPrefix.
I didn't trace the call chain farther than that, but I assume the JVM always uses these canonicalization routines before accessing the filesystem, and so always hits this code one way or another. If you want to trace the call chain farther yourself, you too can partake in the joys of browsing the JVM source code! Download at: http://download.java.net/openjdk/jdk6/
Windows bypasses that limitation if the path is prefixed with \\?\.
Most likely Java is in fact using UNC paths (\?) internally.
I think this will work only on an English language Windows installation:
System.getProperty("user.home") + "/Desktop";
How can I make this work for non English Windows?
I use a french version of Windows and with it the instruction:
System.getProperty("user.home") + "/Desktop";
works fine for me.
I think this is the same question... but I'm not sure!:
In java under Windows, how do I find a redirected Desktop folder?
Reading it I would expect that solution to return the user.home, but apparently not, and the link in the answer comments back that up. Haven't tried it myself.
I guess by using JFileChooser the solution will require a non-headless JVM, but you are probably running one of them.
This is for Windows only. Launch REG.EXE and capture its output :
import java.io.*;
public class WindowsUtils {
private static final String REGQUERY_UTIL = "reg query ";
private static final String REGSTR_TOKEN = "REG_SZ";
private static final String DESKTOP_FOLDER_CMD = REGQUERY_UTIL
+ "\"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\"
+ "Explorer\\Shell Folders\" /v DESKTOP";
private WindowsUtils() {}
public static String getCurrentUserDesktopPath() {
try {
Process process = Runtime.getRuntime().exec(DESKTOP_FOLDER_CMD);
StreamReader reader = new StreamReader(process.getInputStream());
reader.start();
process.waitFor();
reader.join();
String result = reader.getResult();
int p = result.indexOf(REGSTR_TOKEN);
if (p == -1) return null;
return result.substring(p + REGSTR_TOKEN.length()).trim();
}
catch (Exception e) {
return null;
}
}
/**
* TEST
*/
public static void main(String[] args) {
System.out.println("Desktop directory : "
+ getCurrentUserDesktopPath());
}
static class StreamReader extends Thread {
private InputStream is;
private StringWriter sw;
StreamReader(InputStream is) {
this.is = is;
sw = new StringWriter();
}
public void run() {
try {
int c;
while ((c = is.read()) != -1)
sw.write(c);
}
catch (IOException e) { ; }
}
String getResult() {
return sw.toString();
}
}
}
or you can use JNA (complete example here)
Shell32.INSTANCE.SHGetFolderPath(null,
ShlObj.CSIDL_DESKTOPDIRECTORY, null, ShlObj.SHGFP_TYPE_CURRENT,
pszPath);
javax.swing.filechooser.FileSystemView.getFileSystemView().getHomeDirectory()
Seems not that easy...
But you could try to find an anwser browsing the code of some open-source projects, e.g. on Koders. I guess all the solutions boil down to checking the HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Desktop path in the Windows registry. And probably are Windows-specific.
If you need a more general solution I would try to find an open-source application you know is working properly on different platforms and puts some icons on the user's Desktop.
You're just missing "C:\\Users\\":
String userDefPath = "C:\\Users\\" + System.getProperty("user.name") + "\\Desktop";
public class Sample {
public static void main(String[] args) {
String desktopPath =System.getProperty("user.home") + "\\"+"Desktop";
String s = "\"" + desktopPath.replace("\\","\\\\") + "\\\\" +"satis" + "\"";
System.out.print(s);
File f = new File(s);
boolean mkdir = f.mkdir();
System.out.println(mkdir);
}
}
there are 2 things.
you are using the wrong slash. for windows it's \ not /.
i'm using RandomAccesFile and File to manage fles and folders, and it requires double slash ( \\ ) to separate the folders name.
Simplest solution is to find out machine name, since this name is only variable changing in path to Desktop folder. So if you can find this, you have found path to Desktop. Following code should do the trick - it did for me :)
String machine_name = InetAddress.getLocalHost().getHostName();
String path_to_desktop = "C:/Documents and Settings/"+machine_name+"/Desktop/";
I've been using the following code to open Office Documents, PDF, etc. on my windows machines using Java and it's working fine, except for some reason when a filename has embedded it within it multiple contiguous spaces like "File[SPACE][SPACE]Test.doc".
How can I make this work? I'm not averse to canning the whole piece of code... but I'd rather not replace it with a third party library that calls JNI.
public static void openDocument(String path) throws IOException {
// Make forward slashes backslashes (for windows)
// Double quote any path segments with spaces in them
path = path.replace("/", "\\").replaceAll(
"\\\\([^\\\\\\\\\"]* [^\\\\\\\\\"]*)", "\\\\\\\"$1\"");
String command = "C:\\Windows\\System32\\cmd.exe /c start " + path + "";
Runtime.getRuntime().exec(command);
}
EDIT: When I run it with the errant file windows complains about finding the file. But... when I run the command line directly from the command line it runs just fine.
If you are using Java 6 you can just use the open method of java.awt.Desktop to launch the file using the default application for the current platform.
Not sure if this will help you much... I use java 1.5+'s ProcessBuilder to launch external shell scripts in a java program. Basically I do the following: ( although this may not apply because you don't want to capture the commands output; you actually wanna fire up the document - but, maybe this will spark something that you can use )
List<String> command = new ArrayList<String>();
command.add(someExecutable);
command.add(someArguemnt0);
command.add(someArgument1);
command.add(someArgument2);
ProcessBuilder builder = new ProcessBuilder(command);
try {
final Process process = builder.start();
...
} catch (IOException ioe) {}
The issue may be the "start" command you are using, rather than your file name parsing. For example, this seems to work well on my WinXP machine (using JDK 1.5)
import java.io.IOException;
import java.io.File;
public class test {
public static void openDocument(String path) throws IOException {
path = "\"" + path + "\"";
File f = new File( path );
String command = "C:\\Windows\\System32\\cmd.exe /c " + f.getPath() + "";
Runtime.getRuntime().exec(command);
}
public static void main( String[] argv ) {
test thisApp = new test();
try {
thisApp.openDocument( "c:\\so\\My Doc.doc");
}
catch( IOException e ) {
e.printStackTrace();
}
}
}