Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java, Runtime.exec or ProcessBuilder: how to know if the file is shell or binary?

I'm looking into a most efficient way to decide:

  • Should I preprend the user-provided command line with the shell executable
  • If yes, what would that executable be? (/bin/sh? /usr/bin/perl? /usr/bin/ksh? c:/../cmd.exe?)

It is known that to start a shell script from Java one should start the shell instead:

ProcessBuilder pb = new ProcessBuilder("/bin/sh", "script.sh", "arg1", "arg2);

To start a binary one should start the binary itself:

ProcessBuilder pb = new ProcessBuilder("/path/binary", "arg1", "arg2);

If a binary is executed with shell, it produces an error:

ProcessBuilder pb = new ProcessBuilder("/bin/sh", "/path/binary", "arg1", "arg2);
(sh: cannot execute binary file)

If a shell script is executed without the shell binary, it produces an error:

ProcessBuilder pb = new ProcessBuilder("script.sh", "arg1", "arg2);
(error 2: file not found)

I'm in a situation where my application doesn't know what it starts, binary or a script.

The started application is a event handler provided by the end user. It most likely be a shell script executed under Unix; but it can be a *.cmd under Windows, or a Perl script executed under some obscure platform. It is Java, after all.

My first naive attempt was to start the command line with the shell, and see if it works. If not, try to execute it as a binary.

This is ugly and risky: under some unknown combination of platform and shell the second ran may still execute the script, second time, with unpredictable results.

Also, I cannot tell when the script started OK and failed due to some issue of its own from when I just cannot start it.

The best thing I'm considering now is:

  • Read the script and look for any unprintable bytes
  • If found, consider it a binary
  • If not, add /bin/sh (or cmd.exe if under Windows)

Please advise if you have any better ideas in mind.

UPDATE/PARTIAL SOLUTION

Thanks to everyone who shared their thoughts with me.

Turns out, I have confused myself and the rest of the Internet :)

It is not required to prepend the shall binary before the user-entered command line providing that:

  1. the script is in PATH
  2. (for Unix) the script is executable
  3. (for Unix) the script has #!/path/to/interpreter

While I was testing my code, one or another of these conditions did not met. :-(

After performing the test carefully from the scratch script has been executed.

Point 3 is only can be done by the user, and have to be documented in User Manual.

Due to the way these scripts are propagated to the target system, they may be not executable and may not be in PATH.

The only path I care about is relative one, so it is enough to prepend a ./ to any relative path.

Making the scripts executable under Unix (and any other platform) is a more of a challenge. It is not WORA. Placing /bin/sh in front of it may help, but if I remember right under Solaris the shell will not execute a non-executable script.

I'll post another update later this week.

like image 449
Не морозь меня Avatar asked Jan 09 '12 05:01

Не морозь меня


2 Answers

It should be a red flag that you have to jump through these hoops just to run a command. First because this is getting really complicated, and second because Java was designed to be platform independent. When you are investigating OS-specific hacks to make built-in classes work you should step back and re-examine your assumptions.

ProcessBuilder pb = new ProcessBuilder("script.sh", "arg1", "arg2);
(error 2: file not found)

Notice that the error message is "file not found", not "cannot execute shell script" or some such error. The most likely cause for this error is not that you're executing a script, but that the script can't be found.

If the script is in the current directory then you need to add a ./ in front. If you don't put an explicit path to the executable then the executable must reside in one of the directories in your $PATH environment variable. The current directory . is usually not included in $PATH by default.

ProcessBuilder pb = new ProcessBuilder("./script.sh", "arg1", "arg2);

If the script name is a user-supplied value then I would levy this requirement on the user--you could add the ./ for them, but UNIX programs generally try to avoid being too helpful. If they forget to put ./ then that's their problem!

like image 182
John Kugelman Avatar answered Nov 07 '22 03:11

John Kugelman


One possible solution is to generate a script that wraps the executing script/binary from within your program. That way you know it's always a script. The generated script simply executes the inner script/binary and returns the error code (and possibly redirects input/output). When finished, you can simply delete it. Java allows you to create temporary files very easily.

like image 37
Amir Afghani Avatar answered Nov 07 '22 03:11

Amir Afghani