I have a perl CGI that calls a java application which in turn checks a Mysql database. If I search for an entry that does not exist, the java application displays an exception handler message on the server (requires an X display window). The exception is straight forward and understandable, however must be clicked to close it, at which point the perl CGI can continue. Customers of course cannot (and should not) see the exception message.
My question is.. how can I prevent the exception message from displaying on the server window and preventing the CGI from continuing? Is there a way to close the message from perl? I have control over the perl script, but not the java application I call.
$ENV{'DISPLAY'} = 'myserver:0.0';
$testline = system("java -Dby.product=true -jar javaApp.jar $version status>mytest.txt") >> 8;
if $version doesn't exist, I get the exception.
I pipe the results to a file for later file handling in perl
Thanks.
Rocky.
=====================
Thanks.
I added this...
$ENV{'DISPLAY'} = 'server:0.0';
use IPC::Open2;
use POSIX ":sys_wait_h";
$pid = open2(\*CHLD_OUT, \*CHLD_IN, "java -Dby.product=true -jar javaApp.jar $version status>mytest.txt 2>/tmp/java_error.$$");
sleep(5);
kill('TERM', $pid);
If I use a known value in the database, it works fine, as before.
If I search a nonexistent value, the java message still pops up.
Without the sleep line, the java message does NOT popup. In other words it looks like the pid is killed, but so quickly that the result does not get fed into mytest.txt. I thought the sleep function would give some time for the java app to work and then the kill would remove the popup message. But this does not happen.
It seems likely I will have to request changes to the java application so that it does not display a message on screen in the server.
Corrected and extended
Try
system("java -Dby.product=true -jar javaApp.jar $version status 2>/dev/null >mytest.txt");
It redirects the stderr of java to nowhere. Or redirect to a file (like 2>/tmp/java_error.$$) to save it debugging the error.
If it is on windows use 2>nul.
Or use IPC::Open3 and process both input file handles as You like.
More detailed. I created a simple a.java code which writes to stdout, stderr and throws an exception if an arg is defined:
public class a {
public static void main(String[] args) {
System.out.println("STDOUT");
System.err.println("STDERR");
if (args.length > 0) { int i = 1/0; }
}
};
I compiled and run it (don't care about the gcj warning):
$ gcj -C a.java
$ gij -cp . a
STDOUT
STDERR
$ gij -cp . a x
STDOUT
STDERR
Exception in thread "main" java.lang.ArithmeticException: / by zero
at a.main(a.java:5)
$ gij -cp . a >/dev/null
STDERR
$ gij -cp . a x 2>/dev/null
STDOUT
So the stack dump is written to the stderr as expected, so it can be redirected.
$ perl -e 'system("gij -cp . a x 2>/dev/null")'
STDOUT
The example program with IPC::Open3:
#!/usr/bin/perl
use strict;
use warnings;
use IPC::Open3 'open3';
use Symbol 'gensym';
my ($fcin, $fcout, $fcerr);
$fcerr = gensym;
my $pid = open3 $fcin, $fcout, $fcerr, "gij -cp . a x";
my #out = <$fcout>;
my #err = <$fcerr>;
my $err = waitpid $pid, 0;
print "Exit:", ($err >> 8), "\n";
print "OUT: #out\n";
print "ERR: #err\n";
Output:
Exit:72
OUT: STDOUT
ERR: STDERR
Exception in thread "main" java.lang.ArithmeticException: / by zero
at a.main(a.java:5)
Even the man page of IPC::Open3 suggests to use the IPC::Run package. I tried, but it is not the part of the normal distribution. So You can install it from CPAN if You wish.
Related
This question already has an answer here:
Why does Runtime.exec(String) work for some but not all commands?
(1 answer)
Closed 10 months ago.
I am doing an exercise related to Runtime.exec(), I understand that Runtime.exec is not a shell interpreter, that's why I execute "bash -c 'command'" instead, but for some reason, I can execute commands like ls bash -c 'ls' but not echo or redirection or multiple commands. These does not work:
bash -c 'echo 1234'
bash -c 'ls > abc'
bash -c 'ls;id'
bash -c 'ls -al'
Here is my java code:
import java.util.*;
import java.io.*;
public class runtime {
public static void main(String args[]) throws Exception{
String cmd = args[0];
System.out.println(cmd);
Process p = Runtime.getRuntime().exec(cmd);
OutputStream os = p.getOutputStream();
InputStream in = p.getInputStream();
DataInputStream dis = new DataInputStream(in);
String disr = dis.readLine();
while ( disr != null ) {
System.out.println("Out: " + disr);
disr = dis.readLine();
}
}
}
I run the above commands with the syntax:
java runtime "bash -c 'command'"
This works:
$ java runtime "bash -c 'ls'"
bash -c 'ls'
Out: Main.class
Out: Main.java
Out: runtime.class
Out: runtime.java
I am using openjdk 11.0.15 on Ubuntu 20.04 and zsh.
Can anyone tell me why Runtime doesn't work in this case? Thank you!
Because of shell parsing.
These are all concepts that the OS just does not have:
The concept of 'every space separates one argument from another (and the command from the list of arguments'). The concept that a single string can possibly run anything, in fact; at the OS level that's just not what the 'run this thing' API looks like. When you type commands on the command prompt, your shell app is 'interpreting' these strings into a command execution request.
The concept of figuring out that bash means /bin/bash, i.e. $PATH resolution.
The concept that *.txt is supposed to refer to all files that ends in .txt.
The concept that $FOO should be replaced with the value of the environment variable 'FOO'
The concept that ; separates 2 commands, and it's supposed to run both.
The concept that single and double quotes escape things. "Escape things" implies that things can cause interpretation to happen. The OS interprets nothing, therefore there's nothing to escape. Obvious, then, that the OS doesn't know what ' is, or ".
That >foo means: Please set the standard output of the spawned process such that it sends it all to file 'foo'.
In windows shells, that # in front of the command means 'do not echo the command itself'. This and many other things are shellisms: /bin/bash does that, maybe /bin/zsh does something else. Windows's built in shell thing definitely is quite different from bash!
Instead, an OS simply wants you to provide it a full path to an executable along with a list of strings, and pick targets for standard in, standard out, and standard err. It does no processing on any of that, just starts the process you named, and passes it the strings verbatim.
You're sort of half there, as you already figured out that e.g. ls >foo cannot work if you execute it on its own, but it can work if you tell bash to execute it. As ALL of that stuff in the above list? Your shell does that.
It gets more complicated: Turning *.txt into foo.txt bar.txt is a task of bash and friends, e.g. if you attempted to run: ls '*.txt' it does not work. But on windows, it's not the shell's job; the shell just passes it verbatim to dir, and it is the command's job to undo it. What a mess, right? Executing things is hard!
So, what's wrong here? Two things:
Space splitting isn't working out.
Quote application isn't being done.
When you write:
bash -c 'ls >foo'
in a bash shell, bash has to first split this up, into a command, and a list of arguments. Bash does so as follows:
Command: bash
arg1: -c
arg2: ls >foo
It knows that ls >foo isn't 2 arguments because, effectively, "space" is the bash operator for: "... and here is the next argument", and with quotes (either single or double), the space becomes a literal space instead of the '... move to next argument ...' operator.
In your code, you ask bash to run java, and then java to run bash. So, bash first does:
cmd: java
arg1: bash -c 'ls >foo'
With the same logic at work. Your java app then takes that entire string (that's args[0]: "bash -c 'ls >foo'"), and you then feed it to a method you should never use: Runtime.exec(). Always use ProcessBuilder, and always use the list-based form of it.
Given that you're using the bad method, you're now asking java to do this splitting thing. After all, if you just tell the OS verbatim, please run "bash -c 'ls >foo'", the OS dutifully reports: "I'm sorry, but file ./bash -c ;ls >foo' does not exist", because it does not processing at all". This is unwieldy so java's exec() method is a disaster you should never use: Because people are confused about this, it tries to do some extremely basic processing, except every OS and shell is different, java does not know this, so it does a really bad job at it.
Hence, do not use it.
In this case, java doesn't realize that those quotes mean it shouldn't split, so java tells the OS:
Please run:
cmd: /bin/bash (java DOES do path lookup; but you should avoid this, do not use relative path names, you should always write them out in full)
arg1: -c
arg2: 'ls
arg3: >foo'
and now you understand why this is just going completely wrong.
Instead, you want java to tell the OS:
cmd: /bin/bash
arg1: -c
arg2: ls >foo
Note: ls >foo needs to be one argument, and NOTHING in the argument should be containing quotes, anywhere. The reason you write:
/bin/bash -c 'ls >foo'
In bash, is because you [A] want bash not to treat that space between ls and >foo as an arg separator (you want ls >foo to travel to /bin/bash as one argument), and [B] that you want >foo to just be sent to the bash you're spawning and not to be treated as 'please redirect the output to file foo' at the current shell level.
Runtime.exec isn't a shell, so the quotes stuff? Runtime.exec has no idea.
This means more generally your plan of "I shall write an app where the entire argument you pass to it is just run" is oversimplified and can never work unless you write an entire quotes and escaper analyser for it.
An easy way out is to take the input, write it out to a shell script on disk, set the exec flag on it, and always run /bin/bash -c /path/to/script-you-just-wrote, sidestepping any need to attempt to parse anything in java: Let bash do it.
The ONE weird bizarro thing I cannot explain, is that literally passing 'ls' to /bin/bash -c, with quotes intact, DOES work and runs ls as normal, but 'ls *' does not, perhaps because now bash thinks you want executable file /bin/ls * which obviously does not exist (a star cannot be in a file name, or at least, that's not the ls executable, and it's not an alias for the ls built-in). At any rate, you want to pass ls without the quotes.
Let's try it!
import java.util.*;
import java.io.*;
public class runtime {
public static void main(String args[]) throws Exception{
ProcessBuilder pb = new ProcessBuilder();
pb.command("/bin/bash", "-c", "echo 1234");
// pb.command("/bin/bash", "-c", "'echo 1234'");
Process p = pb.start();
OutputStream os = p.getOutputStream();
InputStream in = p.getInputStream();
DataInputStream dis = new DataInputStream(in);
String disr = dis.readLine();
while ( disr != null ) {
System.out.println("Out: " + disr);
disr = dis.readLine();
}
int errCode = p.waitFor();
System.out.println("Process exit code: " + errCode);
}
}
The above works fine. Replace the .command line with the commented out variant and you'll notice it does not work at all, and you get an error. On my mac I get a '127' error; perhaps this is bash reporting back: I could not find the command you were attempting to execute. 0 is what you're looking for when you invoke waitFor: That's the code for 'no errors'.
I'm trying to parse my command line arguments using the apache commons CLI. It might be a bit heavy handed for the example here, but it makes sense in the context of the program I'm creating. I'm trying to read a file pattern filter, similar to what grep uses to select files to process.
My Argument looks like this:
Program --input *.*
I've written a test program to see what the parser is seeing;
public static void main(String[] args) {
Options options = new Options();
options.addOption(new Option(INPUT_FILTER_SHORT, INPUT_FILTER_LONG, true, INPUT_FILTER_DESCRIPTION));
CommandLineParser parser = new BasicParser();
CommandLine cmd = parser.parse(options, args);
System.out.println(cmd.getOptionValue(INPUT_FILTER_SHORT));
}
This prints out:
.classpath
If I change my arguments to:
Program --input test.txt
I get the output:
test.txt
I'm assuming that I have to do something to tell apache commons what * is not a special character? I can't seem to find anything about this online.
I'm experiencing this on Windows (7). I'm fairly certain it's the *.* which is causing the issue as when I swap to using patterns that don't use *, the expected pattern shows up.
Your problem isn't really to do with Commons CLI, but to do with how the shell and the Java executable together process the parameters.
To eliminate other factors, and see what's going on, use a short Java program:
public class ArgsDemo {
public static void main(String[] args) {
for(int i=0; i<args.length; i++) {
System.out.println("" + i + ": " + args[i]);
}
}
}
Play with java ArgsDemo hello world, java ArgsDemo * etc. and observe what happens.
On UNIX and Linux:
Java does no special processing of *. However, the shell does. So if you did:
$ mkdir x
$ cd x
$ touch a b
$ java -jar myjar.jar MyClass *
... then MyClass.main() would be invoked with the parameter array ["a","b"] -- because the UNIX shell expands * to files in the current directory.
You can suppress this by escaping:
$ java -jar myjar MyClass * // main() sees ["*"])
(Note that a UNIX shell wouldn't expand *.* to .classpath because this form would ignore "hidden" files starting with .)
On Windows
cmd.exe does not do UNIX-style wildcard expansion. If you supply * as a parameter to a command in Windows, the command gets a literal *. So for example, PKUNZIP *.zip passes *.zip to PKUNZIP.EXE, and it's up to that program to expand the wildcard if it wants to.
Since some release of Java 7, the Java executable for Windows does some wildcard to filename expansion of its own, before passing the parameters to your main() class.
I've not been able to find clear documentation of Java-for-Windows' wildcard expansion rules, but you should be able to control it with quoting, escaping the quotes to prevent cmd.exe interpreting them:
> java.exe -jar myjar.jar MyClass """*.*"""
(Untested as I don't have a Windows box handy, and quoting in cmd.exe is a bit of a beast - do please experiment and either edit the above or leave a comment)
I am running following unix command using ProcessBuilder and it is working fine.
String[] commands = {"egrep","search string","fileName"}
ProcessBuilder pb =...
Now I have an additional requirement to filter the output.
String [] commands = {"egrep","search string","fileName","|","awk\'$0 >\"time\" && $2==\"INFO\"\'"}
I am getting error "IO Exception: No such file error". It seems it is considering pipe(|) and other awk command as file.
I also tried adding prefix "bash -c" or "/bin/sh -c" like
String [] commands = {"bash","-c","egrep","search string","fileName","|","awk\'$0 >\"time\" && $2==\"INFO\"\'"}
Now I am getting error
"bash -c line0: syntax error near unexpected token 'is'bash:-c line0:"
I also tried giving entire egrep command in single string but it also didn't work.
Please advice what am I missing error to use pipe for filtering the output.
Without considering whether your proposed egrep/awk command line will work - I'm unsure it's what you intend - consider making the full command line a single argument to the -c option of bash, so you'll have only three strings involved in the construction of the ProcessBuilder. See SO question 3776195 for some more info.
C code
#include <stdio.h>
int main() {
printf("Expected to print");
int a = 1/0;
return 0;
}
compile it using
gcc Test.c
From java running
p = Runtime.getRuntime().exec(cmd);
here cmd is [/bin/bash, -c, ./a.out]
and then i capture the errorStream of the process p.getErrorStream()
but errorStream does not have the Floating point exception in it which we get if we run
./a.out
from console
Floating point exception isn't printed from the program but by bash. If you interactively start the program and the program got the FPE bash prints the FPE message.
With bash -c a.out bash just calls one of the exec() functions without calling fork() before and thus is directly replaced by a.out hence it can't print out anything.
I get Floating point exception (core dumped) in the Java error stream if I use Runtime.getRuntime().exec(new String[]{"/bin/bash","-i","-c","echo -n;./a.out"});.
With the echo -n,which does nothing, bash is forced to stay alive and so can print out the FPE message. The -i sets the bash to interactive mode.
I have a java program which is supposed to launch a shell script. The script contains 6 tasks which are to be executed in sequence. The java program launches the script and it starts(as I see the logs). But after 10-15 seconds, the execution stops, even before the first task in the shell script is completed. The strange thing is that the script runs fine when I launch it in terminal. To avoid risking the program to hang while the script is being executed, I launch it in a separate thread. What might be a probable reason?
Java code -
try {
log.info("run cmd - "+optionsRun);
String[] cmdLine = (String[]) optionsRun.toArray(new String[optionsRun.size()]);
Process process = Runtime.getRuntime().exec(cmdLine);
log.info("end run cmd " + this.getScriptPath());
//
// BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
// writer.write("mypwd");
// writer.flush();
// writer.close();
InputStream is = process.getErrorStream();
String error = inputStreamToStringValue(is);
log.trace("Eventual error was : " + error);
InputStream os = process.getInputStream();
String output = inputStreamToStringValue(os);
log.info("Eventual output was : " + output);
if (error!=null & error.length()>0) {
throw new ActionProcessingException("An error occurred when running the script :'"+this.getScriptPath()+"' with following error message : "+error);
}else {
log.info("Script run ended successfully.");
}
And the shell script looks this way -
#!/bin/sh
# ./publish <path-to-publish-home-folder> <workspace_id> <start_date> <end_date>
# ./publish <path-to-publish-home-folder> 100011 2010-01-06-12:00:00-CET 2012-01-14-19:00:00-CET
rm -f $1/publish.log
echo 'Start publish' >> $1/publish.log
echo $0 $1 $2 $3 $4 $5 $6 $7 $8 $9 >> $1/publish.log
# lancement de l'export RDF du domaine
cd $1/1-export-domain
echo "Starting export domain with the command - ./export.sh $2" >> $1/publish.log
./export.sh $2
# lancement de l'export des translations du domaine
cd $1/2-export-trans
echo "Starting export domain(translated) with the command - ./export.sh $2" >> $1/publish.log
./export.sh $2
.....
.....
a couple of more steps like 1 and 2
....
Thanks in advance,
I'm not sure, but I'll recommend two links that might help you figure it out.
The first is a very old one about Runtime.exec():
http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html
The second is about ProcessBuilder, the new class intended to replace Runtime.exec():
http://www.java-tips.org/java-se-tips/java.util/from-runtime.exec-to-processbuilder.html
I cannot be sure I my guess is that the problem is in your method inputStreamToStringValue(is). It reads STDERR and it is blocking on read. When it has nothing to read from STDERR but the process is not terminated yet you will be blocked forever.
I'd recommend you to use ProcessBuilder:
ProcessBuilder b = new ProcessBuilder();
b.redirectErrorStream(true);
Now you can read STDIN and STDERR together.
If you still want to read them separately you have 2 solutions.
First do as you are doing now but do not be block on read, i.e. call in.available() before each call of read and then read only number of bytes that were previously available.
Second way is to use the shell redirection. Run you script and redirect its STDOUT and STDERR to temporary files. Then wait until your process terminates and then read from files. I personally think that this solution is easier and more robust.
Good luck.