unexpected result while communicating with another process's stdin - java

My project directory has the 3 files below.
rndbet/rndbet.py
while True:
s = input()
if s == "exit":
exit()
else:
print("I'm rndbet: " + s)
rndbet/start
python3 rndbet.py
mjhd.java
import java.io.PrintStream;
import java.util.Scanner;
public class mjhd {
public static void main(String[] args) throws Exception {
Process process = new ProcessBuilder("bash", "-c", "cd rndbet&&./start").start();
new Thread(new Runnable() {
#Override
public void run() {
Scanner in = new Scanner(process.getInputStream());
while (in.hasNextLine()) {
System.out.println("<- rndbet: " + in.nextLine());
}
}
}).start();
Scanner in = new Scanner(System.in);
PrintStream out = new PrintStream(process.getOutputStream(), true);
while (true) {
out.println(in.nextLine());
} //this part is actually broken; it shouldn't be an infinite loop
//just for testing
}
}
When I type bash -c "cd rndbet&&./start" directly from the command line, below happens.
$ bash -c "cd rndbet&&./start"
hi
I'm rndbet: hi
exit
But running the java program behaves differently.
$ java mjhd
hi
<- rndbet: I'm rndbet: hi
exit
<- rndbet: I'm rndbet: exit
exit
<- rndbet: I'm rndbet: exit
So now the Python script doesn't get the exit command correctly. Please help me fix this problem.
I've just found a problem that when the Python script is run via Java, an extra character of ASCII value 13 is always appended at the end of the sent text. What is a possible fix?

Okay I found a simple answer.
Changing out.println(in.nextLine()); to out.print(in.nextLine() + '\n') works.

Related

process.waitFor() throws IllegalThreadStateException

Environment
Windows 10
Java 1.8
Process
I am running a 7zip's zip task.
The process takes 2 to 3 hours to complete.
Exception
java.lang.IllegalThreadStateException: process has not exited
at java.lang.ProcessImpl.exitValue(ProcessImpl.java:443)
at java.lang.ProcessImpl.waitFor(ProcessImpl.java:452at
My code
int exitValue = -1;
Process start = null;
try
{
ProcessBuilder processBuilder = new ProcessBuilder(commands);
start = processBuilder.start();
try(BufferedReader ipBuf = new BufferedReader(new InputStreamReader(start.getInputStream())))
{
String line = null;
while ((line = ipBuf.readLine()) != null)
{
LOGGER.info(line);
}
}
try(BufferedReader errBuf = new BufferedReader(new InputStreamReader(start.getErrorStream())))
{
String line;
while ((line = errBuf.readLine()) != null)
{
LOGGER.warning(line);
}
}
start.waitFor();
exitValue = start.exitValue();
}
finally
{
if (start != null)
{
start.destroy();
}
}
return exitValue;
I'm unable to find the root cause of this issue.
Note: I've tried this process with a similar demo instance on the same
machine and it works fine.
Please help me resolve this, Thanks.
There are two parts to your problem:
The JDK has a bug which causes an exception to be thrown when a Windows process returns an exit code of 259.
The command that you pass to ProcessBuilder exits with an exit code of 259 when it shouldn't.
Tackling each point in turn:
The bug in the JDK is caused by the following flawed logic in the Windows-specific implementation of Process.waitFor(): First, it waits until the process exits. Then, it calls exitValue() to get the exit value from the process. But unfortunately exitValue() gets the exit value and then checks if it's a special value that indicates the process hasn't exited. Since waitFor() knows that the process has exited, it should get the exit value directly instead of calling this method which does an unwanted check. Hopefully the JDK developers will fix this bug soon.
You should be using the command-line version of 7-zip, 7z.exe which exits with a set of well-defined exit values (so it never returns 259).
If your problem is caused by the Windows ProcessBuilder exit code 259 bug then there are workarounds available: all you need to do to make sure that your sub-process does not exit with status code 259 and Windows JRE won't report java.lang.IllegalThreadStateException.
You can easily reproduce this issue by executing the following command with Runtime.getRuntime().exec(cmd) or ProcessBuilder(cmd):
String[] cmd = {"cmd.exe /c exit /b 259"};
If you have written the code for the sub-process then just edit your code so that exit code is never set to 259.
If you have not written the code for the sub-process then a rather hacky workaround is to wrap your Java sub-process launch with a "CMD.EXE" and mini-script which adapts non-zero sub-process exit back to exit codes 0 or 1:
String[] fixed = new String[] { "cmd.exe", "/c",
"(call "+String.join(" ", cmd)+ ") || (echo !!! DETECTED ERROR!!! && exit 1)" };
Note: I'm no expert on CMD. The above fix definitely won't work for certain commands or punctuation (eg those with quotes / spaces etc), and because it runs under CMD.EXE environment settings the outcome might be different to direct launch from the calling JVM.
Here is an example class you could test with:
/** Examples to test with and without the fix:
java Status259 "cmd.exe /c exit /b 0"
java Status259 "cmd.exe /c exit /b 25"
java Status259 "cmd.exe /c exit /b 259"
java Status259 %JAVA_HOME%\bin\java -cp your.jar Status259$StatusXXX 0
java Status259 %JAVA_HOME%\bin\java -cp your.jar Status259$StatusXXX 33
java Status259 %JAVA_HOME%\bin\java -cp your.jar Status259$StatusXXX 259
*/
public class Status259 {
public static class StatusXXX {
public static void main(String ... args) {
int status = args.length > 0 ? Integer.parseInt(args[0]) : 0;
System.out.println("StatusXXX exit code: "+status);
System.exit(status);
}
}
public static int exec(String[] cmd) throws IOException, InterruptedException {
System.out.println("exec "+Arrays.toString(Objects.requireNonNull(cmd)));
ProcessBuilder pb = new ProcessBuilder(cmd);
// No STDERR => merge to STDOUT - or call redirectError(File)
pb.redirectErrorStream(true);
Process p = pb.start();
// send sub-process STDOUT to the Java stdout stream
try(var stdo = p.getInputStream()) {
stdo.transferTo(System.out);
}
int rc = p.waitFor();
System.out.println("exec() END pid="+p.pid()+" CODE "+rc +' '+(rc == 0 ? "OK":"**** ERROR ****"));
return rc;
}
public static void main(String ... args) throws IOException, InterruptedException {
// COMMENT OUT NEXT LINE TO SEE EFFECT OF DIRECT LAUNCH:
args = fixStatus259(args);
int rc = exec(args);
System.exit(rc);
}
private static String[] fixStatus259(String[] cmd) {
System.out.println("fixStatus259 "+Arrays.toString(cmd));
return new String[] {
"cmd.exe", "/c",
"(call "+String.join(" ", cmd)+ ") || (echo !!! DETECTED ERROR!!! && exit 1)"
};
}
}

How to execute bash commands in Java (Raspberry pi)

I dont know why, but I can only execute a very small pallet of commands on my Raspberry 3B from code (I cane even execute echo). For some reason, 99% of the commands that you would normally be able to do in the terminal itself, you cant do from code.
Example: I execute this java code:
Runtime.getRuntime().exec("echo hi");
And I get this:
`java.io.IOException: Cannot run program "echo hi": error=2, No such file or directory
Is there a PATH configuration that I dont have access to in java code? why cant I execute any commands to the raspberry pi from code?
I've written some example that uses the exec() call. There are other methods to start processes from within Java (ProcessBuilder is the keyword here), but this example is relatively easy to understand:
import java.util.*;
import java.io.*;
import java.text.*;
public class X {
public static void main(String argv[])
{
String args[] = { "/bin/bash", "-c", "uptime" };
try {
Process p = Runtime.getRuntime().exec(args);
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = in.readLine();
while (line != null) {
System.out.println("Found: " + line);
line = in.readLine();
}
} catch (Exception e) {
System.err.println("Some error occured : " + e.toString());
}
}
}
Basically the program executes the command line /bin/bash -c uptime; just an uptime would have done the same, but I wanted to show how to work with command line arguments for the program to start.

How to have java run terminal commands on Mac? (Echo command)

How do you have java run commands on Mac? I see some examples of complex commands that is hard to follow. If I wanted to run a simple echo command from java, how would I do that? Not using osascript yet. Just want to see how you would send an echo from java to terminal.
public static void main(String[] args) throws IOException {
ProcessBuilder x = new ProcessBuilder("echo"," hi");
x.start();
}
This is the code I tried, but it does not work.
I think this question can help people who are trying to learn the basics of ProcessBuilder.
I am on Windows so the below code uses Windows echo. I hope you know the Mac echo command so that you can replace my command with yours.
import java.io.IOException;
import java.lang.ProcessBuilder;
public class PrcBldT2 {
public static void main(String[] args) {
// This command is for Windows operating system.
// For MacOS, try: new ProcessBuilder("echo", "hi")
ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c", "echo", "hi");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
try {
Process p = pb.start();
int result = p.waitFor();
System.out.println("Exit status = " + result);
}
catch (IOException | InterruptedException x) {
x.printStackTrace();
}
}
}
Note that each word in the command is a separate string. The echo command output will be redirected to System.out.

Exit code of Java not coming in Bash

I have a bash file:
REPOS="$1"
TXN="$2"
SVNLOOK=/usr/bin/svnlook
LOGMSG=$($SVNLOOK log -t "$TXN" "$REPOS" | grep "[a-zA-Z0-9]")
echo "\n$LOGMSG" >> /dev/tty
javac ~/Desktop/SomeClass.java
java ~/Desktop/SomeClass $LOGMSG
STATUS=$?
echo "\n" >> /dev/tty
echo $STATUS >> /dev/tty
exit 0
which calls this java file:
public class SomeClass {
public static void main(String[] args) {
String result = "";
for (String s: args) {
result = result + " " + s;
}
String regex = ".*\\bHello\\b.*";
if(result.matches(regex)) {
System.out.println("It matches");
System.exit(0);
} else {
System.out.println("It does not match");
System.exit(42);
}
}
}
I have never in the Java file have exited with the exit code of 1. However when I echo the status in the bash file, it always shows me '1' What can be the reason behind this?
The error code is because Java is failing to start. You aren't specifying the class to be run correctly.
If I have a class located in my desktop directory, I would need to use the following to run it from another directory:
java -cp ~/Desktop SomeClass
assuming that SomeClass has no package specified. If you have package org.foo.bar; at the top of the file, you would need to use
java -cp ~/Desktop org.foo.bar.SomeClass

Executing command in cmd and showing output in jTextArea of swing GUI application

I am developing a swing gui application in which i want to execute some cmd commands and show their output in a jTextArea and i also want to enter values asked during program execution in a jTextField. Plase provide some code which can help me to achive my goal.
This code should get you started with executing cmd commands from java and capturing their output:
import java.io.InputStream;
public class CmdProxy {
public static void main(String [] args) {
try {
Process proc = Runtime.getRuntime().exec("cmd /C \"dir \\ \"");
InputStream is = proc.getInputStream();
// NOTE: this is not the most elegant way to extract content from the
// input stream
int i = -1;
StringBuilder buf = new StringBuilder();
while ((i = is.read()) != -1) {
buf.append((char)i);
}
proc.waitFor();
System.out.println(buf.toString());
} catch (Throwable t) {
t.printStackTrace();
}
}
}
Try it in your sandbox and see what happens. Note that the /C option ensures the cmd process terminates after it is done handling the command arguments we passed (in this case, "dir \ "). In your case you want to exec "cmd /C \"" + whateverUserSpecified + "\"". Obviously, I'm assuming you're programming in/for a Window's environment. I'll let you or someone else figure out the GUI code.

Categories

Resources