I would like to manage a Shell executor and i have some problems with Windows (not a surprise).
No pb to detect OS, no pb to start Process with ProcessBuilder, no pb to code a StreamGobbler.
But when i want to test it with JUnit, i have a strange behavior...
If i put "cmd.exe" and "/C" commands for Windows, my CMD script is not working. Without all is perfect.
Where is the problem ?
If tomorrow i want to start an exe, i think cmd.exe /c will be necessary completed with start and path to exe.
My Shell executor:
public static boolean execCmd(List<String> cmd, Map<String, String> env, File workingDir, GobblerListener gobblerListener) {
boolean result = false;
if (isWindows()) {
//cmd.add(0, "cmd.exe");
//cmd.add(1, "/C");
} else if (isUnixSolaris()) {
cmd.add(0, "/bin/ksh");
cmd.add(1, "-c");
} else {
logger.debug(ResourceBundle.getBundle(bundleName).getString("system.env.unknown"));
return result;
}
ProcessBuilder pb = new ProcessBuilder(cmd);
Map<String, String> envp = pb.environment();
for (Map.Entry<String, String> entry : env.entrySet()) {
envp.put(entry.getKey(), entry.getValue());
}
if (workingDir != null) {
pb.directory(workingDir);
}
pb.redirectErrorStream(true);
Process p = null;
try {
p = pb.start();
StreamGobbler outputGobbler = new StreamGobbler(p.getInputStream(),
gobblerListener);
outputGobbler.start();
p.waitFor();
outputGobbler.join();
int exitValue = p.exitValue();
result = (exitValue == 0 ? true : false);
} catch (InterruptedException ex) {
logger.error(ResourceBundle.getBundle(bundleName).getString(
"system.env.exec.interrupted"));
} catch (IOException e) {
logger.error(
ResourceBundle.getBundle(bundleName).getString(
"system.env.exec.exception"), e);
} finally {
if (p != null) {
p.destroy();
}
}
return result;
}
Here my StreamGobbler:
public class StreamGobbler extends Thread {
private static final Logger logger = LoggerFactory.getLogger(GobblerListener.class);
public interface Listener {
public void onLine(String line);
}
private Listener listener = null;
private InputStream is = null;
private BufferedReader reader = null;
public StreamGobbler(InputStream is, Listener onLineListener) {
this.is = is;
this.listener = onLineListener;
}
#Override
public void run() {
// keep reading the InputStream until it ends (or an error occurs)
try {
reader = new BufferedReader(new InputStreamReader(is));
try {
String line = reader.readLine();
while (line != null) {
if (listener != null && !line.trim().isEmpty())
listener.onLine(line);
line = reader.readLine();
}
} finally {
reader.close();
}
} catch (IOException e) {
logger.error("", e);
}
}
}
Thank you.
Related
Hey all not sure if there is a solution to my situation but here goes.
I am sending some 9 cmd.exe commands:
static String startDaemon_2 = "scm daemon start"; //Start Daemon
static String isRQRunning_3 = "RQAdapter.bat status"; //Check if RQ is running
static String startRQ_4 = "RQAdapter.bat start"; //Start RQ
...etc etc....
And this is my main code to fire off the commands:
static ProcessBuilder processBuilder = new ProcessBuilder();
static Process process = null;
static BufferedReader reader = null;
public static Object steps(String cmds, String dir_) {
processBuilder.command("cmd.exe", "/c", cmds);
processBuilder.directory(new File(dir_));
try {
process = processBuilder.start();
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = "";
while ((line = reader.readLine()) != null) {
System.out.println(line);
if (line.contains("Key:")) {
return true;
} else if (line.contains("successfully")) {
return true;
} else if (line.contains("background")) {
return true;
} else if (line.contains("not")) {
//Load the adapter then
return "startRQM_4";
} else if (line.contains("sandbox")) {
return true;
} else if (line.contains("Misfit")) {
return true;
// repair goes here
} else if (line.contains("Unresolved")) {
//at this point theres no need to carry on. theres a conflick
return "fix";
}
}
int exitCode = process.waitFor();
System.out.println(line);
if (exitCode == 0) {
return true;
} else {
return false;
}
} catch (IOException e) {
e.printStackTrace();
return false;
} catch (InterruptedException e) {
e.printStackTrace();
return false;
}
}
And I call that function this way:
result = steps(startDaemon_2, scmDir);
if (result == "false") { System.out.println("not true"); }
result = steps(isRQMRunning_3, scmDir);
etc..... etc......
When I fire these same commands off in an actual cmd window it works just fine. Does what it should do. However, when the java program does the exact same thing it seems not to work even though there are no error.
Then I started thinking that maybe I need to define ProcessBuilder, process, reader
But it seems to still not work and without errors.
Is there some other type of way to do a few cmd.exe in the same setting? Seems it's just doing new stuff for each command.
What I mean by the last sentence is that I log in using the first command and then it goes to the other one which is start daemon, etc. But it seems to act like its brand new for each call (a.k.a. Isn't not logged in when firing the 2nd command, etc...)
In Java you can call a shell file like this:
public class Shell {
private static Shell rootShell = null;
private final Process proc;
private final OutputStreamWriter writer;
private Shell(String cmd) throws IOException {
this.proc = new ProcessBuilder(cmd).redirectErrorStream(true).start();
this.writer = new OutputStreamWriter(this.proc.getOutputStream(), "UTF-8");
}
public void cmd(String command) {
try {
writer.write(command+'\n');
writer.flush();
} catch (IOException e) { }
}
public void close() {
try {
if (writer != null) {
writer.close();
if(proc != null) {
proc.destroy();
}
}
} catch (IOException ignore) {}
}
public static void exec(String command) {
Shell.get().cmd(command);
}
public static Shell get() {
if (Shell.rootShell == null) {
while (Shell.rootShell == null) {
try {
Shell.rootShell = new Shell("su"); //Open with Root Privileges
} catch (IOException e) { }
}
}
return Shell.rootShell;
}
}
Shell.exec("echo " + bt.getLevel() + " > "+ flashfile);
right.
but I have a shell which giving an argument after executing it.
how can I pass that argument? I don't want user type anything to run this shell file. in another word, I want to fully automate a shell file.
If you want to automate a shell file with a Java programme, this can be done. You could even pipe a series of commands to this programme saved in a file and executing these as a batch.
You can execute commands batches of commands from like this:
java -cp experiments-1.0-SNAPSHOT.jar ConsoleReader < commands.txt
commands.txt is a file with a series of commands:
cmd /k date
cmd /k dir
netstat
ipconfig
Or you can with the same programme allow the user to execute commands on the command line.
Below you can find a sample programme which you can compile and be run in the above described manner.
What does it do?
It hooks a java.util.Scanner to the console input and consumes each line.
Then it spawns two threads which listen to the error and input streams and write out either to stderr or stdin.
Empty lines on the console are ignored
If you type "read " it will execute the commands on that file.
Source:
public class ConsoleReader {
public static void main(String[] args) throws IOException, DatatypeConfigurationException {
try(Scanner scanner = new Scanner(new BufferedInputStream(System.in), "UTF-8")) {
readFromScanner(scanner);
}
}
private static final Pattern FILE_INPUT_PAT = Pattern.compile("read\\s*([^\\s]+)");
private static void readFromScanner(Scanner scanner) {
while (scanner.hasNextLine()) {
try {
String command = scanner.nextLine();
if(command != null && !command.trim().isEmpty()) {
command = command.trim();
if("exit".equals(command)) {
break; // exit shell
}
else if(command.startsWith("read")) { // read from file whilst in the shell.
readFile(command);
}
else {
Process p = Runtime.getRuntime().exec(command);
Thread stdout = readFromStream(p.getInputStream(), System.out, "in");
Thread stderr = readFromStream(p.getErrorStream(), System.err, "err");
stdout.join(200);
stderr.join(200);
}
}
}
catch(Exception e) {
Logger.getLogger("ConsoleReader").log(Level.SEVERE, String.format("Failed to execute command %s", e));
}
}
}
private static void readFile(String command) throws FileNotFoundException {
Matcher m = FILE_INPUT_PAT.matcher(command);
if(m.matches()) {
String file = m.group(1);
File f = new File(file);
if (f.exists()) {
try (Scanner subScanner = new Scanner(f)) {
readFromScanner(subScanner);
}
}
}
else {
System.err.printf("Oops, could not find '%s'%n", command);
}
}
private static Thread readFromStream(InputStream stdin, PrintStream out, String name) throws IOException {
Thread thread = new Thread(() -> {
try (BufferedReader in = new BufferedReader(new InputStreamReader(stdin))) {
String line;
while ((line = in.readLine()) != null) {
out.println(line);
}
} catch (IOException e) {
Logger.getLogger("ConsoleReader").log(Level.SEVERE, "Failed to read from stream.", e);
}
}, name);
thread.setDaemon(true);
thread.start();
return thread;
}
}
Runtime.getRuntime().exec("src/[FILE LOCATION]");
I think this is the command you're looking for. Let me know if it works!
I am getting "0" value while calling exitValue() for java process object in linux but occasionally child threads (error & output stream readers) are not completed and getting stuck in join. Shouldn't "0" value of exitValue() of process guarantee that all sub processes terminated successfully?
private class ReadStdoutThread extends Thread {
private Process m_prc;
private StringBuffer m_sb;
public ReadStdoutThread(Process prc, StringBuffer sb) {
m_prc = prc;
m_sb = sb;
}
public void run() {
BufferedReader stdout =
new BufferedReader(new InputStreamReader(m_prc.getInputStream()));
String line = null;
try {
while ((line = stdout.readLine()) != null) {
System.out.println("Stdout: " + line);
m_sb.append(line + "\n");
}
stdout.close();
} catch (IOException e) {
System.out.println(e.toString());
return;
}
}
}
private class ReadStderrThread extends Thread {
private Process m_prc;
private StringBuffer m_sb;
public ReadStderrThread(Process prc, StringBuffer sb) {
m_prc = prc;
m_sb = sb;
}
public void run() {
BufferedReader stderr =
new BufferedReader(new InputStreamReader(m_prc.getErrorStream()));
String line = null;
try {
while ((line = stderr.readLine()) != null) {
System.out.println("Stderr: " + line);
m_sb.append(line + "\n");
}
stderr.close();
} catch (IOException e) {
System.out.println(e.toString());
return;
}
}
}
public static String runCmd(String cmd, long timeoutMS) throws IOException,
InterruptedException {
Process prc = Runtime.getRuntime().exec(cmd);
long startTimeMS = System.currentTimeMillis();
boolean isRunning = true;
System.out.println("Command has started.");
StringBuffer sb = new StringBuffer();
ReadStdoutThread ot = new HostCommand().new ReadStdoutThread(prc, sb);
ReadStderrThread et = new HostCommand().new ReadStderrThread(prc, sb);
ot.start();
et.start();
if (timeoutMS == 0) {
System.out.println("Thread will wait until command is completed.");
prc.waitFor();
} else {
System.out.println("Command timeout (ms): " + timeoutMS);
synchronized (prc) {
int n = -1;
while (isRunning) {
prc.wait(1000);
try {
n = prc.exitValue();
System.out.println("Command has completed with value: " +
n);
m_processExitValue = n;
isRunning = false;
} catch (IllegalThreadStateException e) {
// command is still running
isRunning = true;
}
if ((System.currentTimeMillis() - startTimeMS >
timeoutMS) && isRunning) {
System.out.println("Timeout has reached, and command is still running. Command will be interrupted.");
prc.destroy();
m_processExitValue = n;
isRunning = false;
}
}
}
}
try {
ot.join(timeoutMS);
}
catch(InterruptedException e) {
throw e;
}
try {
et.join(timeoutMS);
}
catch(InterruptedException e) {
throw e;
}
return sb.toString();
}
According to the documentation, the method should return a IllegalThreadStateException if any subprocess is not yet terminated, otherwise it will return 0 which indicates normal termination.
Source: https://docs.oracle.com/javase/7/docs/api/java/lang/Process.html
There is no guarantee that the ReadStdoutThread and ReadStderrThread have finished execution. There is no sync between Process's waitFor API and the two seperate threads that you have spawned to read the standard input and error streams from the Process. You need to use : ot.join();et.join(); after invoking ot.start();et.start();.Basically, join should be invoked before calling Process.waitFor()
I want a solution for printing value of process variable p. How can we print value of p? Currently value of p is: java.lang.UNIXProcess#727896
public class shellscript{
public static void main(String[] args) {
Runtime r = Runtime.getRuntime();
Process p = null;
String cmd[] = {
"/bin/bash",
"/home/aminul/myscript"
};
try {
p = r.exec(cmd);
System.out.println("testing..." + p);
System.out.println(p);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
If you want to log the standard output and the exit code of the process, try the following:
public static void main(String[] args) throws Exception
{
final Runtime r = Runtime.getRuntime();
final String cmd[] = { "/bin/bash", "/home/aminul/myscript" };
try
{
final Process p = r.exec(cmd);
new Thread()
{
public void run()
{
BufferedReader br = null;
try
{
br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = br.readLine();
while (line != null)
{
System.out.println(line);
line = br.readLine();
}
}
catch (final Exception e)
{
e.printStackTrace();
}
finally
{
if (br != null)
try
{
br.close();
}
catch (final IOException ioe)
{
ioe.printStackTrace();
}
}
};
}.start();
p.waitFor();//wait for process to terminate
System.out.println("Exit code: "+p.exitValue());
}
catch (Exception e)
{
e.printStackTrace();
}
}
Of course, if you want to log the ErrorStream as well, you will have to start another thread.
Process don't have name attribute. But you can use pid.
You can try it in this way
Field field=p.getClass().getField("pid"); // use reflection since pid is private
System.out.println(field);
But you can't use
System.out.println(p)
Since Process don't have a override toString() method
String run="c:\\Program Files\DOSBox-0.74\dosbox.exe dosbox -c mount c c:\games";
The word c c:\games gets removed.
Please advise how do I prevent this? Should I use a literal to insert the spaces in the command?
A little bit of experimentation...
public class Test {
public static void main(String[] args) {
try {
ProcessBuilder pb = new ProcessBuilder("C:\\Program Files (x86)\\DOSBox-0.74\\DOSBox.exe", "dosbox", "-c", "mount c c:\\games");
pb.redirectError();
Process p = pb.start();
new Thread(new InputStreamConsumer(p.getInputStream())).start();
System.out.println("Have exited with " + p.waitFor());
} catch (IOException | InterruptedException exp) {
exp.printStackTrace();
}
}
public static class InputStreamConsumer implements Runnable {
private InputStream is;
public InputStreamConsumer(InputStream inputStream) {
is = inputStream;
}
#Override
public void run() {
int in = -1;
try {
while ((in = is.read()) != -1) {
System.out.print((char) in);
}
} catch (IOException exp) {
exp.printStackTrace();
}
}
}
}