Passing to Linux's "here string" via Java - java

I am trying to pass a string to a Linux command using <<<:
cat <<< 'Hello'
While this works perfectly in the terminal, Java does not execute this
String cmd = "cat <<< 'Hello'";
Process p = new ProcessBuilder(cmd.split(" ")).start();
String stderr = IOUtils.toString(p.getErrorStream(), Charset.defaultCharset());
String stdout = IOUtils.toString(p.getInputStream(), Charset.defaultCharset());
System.out.println(stderr);
with an error from terminal:
cat: '<<<': No such file or directory
cat: "'hello'": No such file or directory
Why is that so? Commands without <<< get executed in the usual manner.

As others have pointed out, <<< is a capability of shells, like bash. You could invoke bash from your ProcessBuilder… but really, you don’t need a here-string. You have Java.
Here’s how to pass known input to a command:
String input = "Hello";
ProcessBuilder builder = new ProcessBuilder("cat");
builder.inheritIO();
builder.redirectOutput(ProcessBuilder.Redirect.PIPE);
Process p = builder.start();
try (Writer processInput =
new OutputStreamWriter(p.getOutputStream(), Charset.defaultCharset())) {
processInput.write(input);
}
The inheritIO method of ProcessBuilder will cause the subprocess’s standard output to appear in the Java program’s standard output, and similarly, the subprocess’s standard error will appear in the Java program’s standard error.
If you want to capture the standard output of the process, you can replace inheritIO() with an explicit call to redirectError:
String input = "Hello";
ProcessBuilder builder = new ProcessBuilder("cat");
builder.redirectError(ProcessBuilder.Redirect.INHERIT);
Process p = builder.start();
try (Writer processInput =
new OutputStreamWriter(p.getOutputStream(), Charset.defaultCharset())) {
processInput.write(input);
}
try (BufferedReader processOutput = new BufferedReader(
new InputStreamReader(p.getInputStream(), Charset.defaultCharset()))) {
String line;
while ((line = processOutput.readLine()) != null) {
// ...
}
}

Try this way:
processBuilder.command("bash", "-c", cmd);
In general output redirection is shell feature and ProcessBuilder is not able to understand the command in the way you gave it.

Related

Junit5 is unable to execute shell commands within tests

I had question about running scripts using Junit 5. I have the following piece of code:
public class RunMvnSubprocess {
#Test
public void main() throws IOException, InterruptedException {
String[] cmd = new String[]{"mvn.cmd", "-version"}; // command to be executed on command prompt.
Process p = Runtime.getRuntime().exec(cmd);
try (BufferedReader output = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
String line;
while ((line = output.readLine()) != null) {
System.out.println(line);
}
}
p.waitFor();
}
}
I get no output when I run it using Junit 5.7.0. However, running this on Junit 4.13.2 works fine.
Please note that I am running this piece of test in Windows 10 Pro version 21H1.
EDIT:
Modifying
new String[]{"mvn.cmd", "-version"}
to
new String[]{"cmd", "/c", "\"mvn -version\""}
works for me, but launching a subshell is a bad practice so I am keeping this workaround as a last resort.
Note that you are implicity running a sub-shell as the Windows command CMD.EXE is called to interpret the contents of mvn.cmd, so your value of cmd is equivalent to:
cmd = new String[]{ "cmd", "/c", "call mvn.cmd -version"};
If you get no error code from waitFor or no output or no exception, then the issue will be reported in the STDERR stream. Change to use ProcessBuilder instead and you can merge STDERR to STDOUT as follows:
ProcessBuilder pb = new ProcessBuilder(cmd);
// No STDERR => merge to STDOUT
pb.redirectErrorStream(true);
Process p = pb.start();
Also, no need to write much code to consume STDOUT:
try(var stdo = p.getInputStream()) {
stdo.transferTo(System.out);
}
int rc = p.waitFor();
if (rc != 0) throw new RuntimeException("test failed");
Hopefully this will explain your problem with the mvn command.

java command execution escapes '|'

I have a function to execute a system command:
public String cmd(String s) {
String out = "";
try {
Runtime run = Runtime.getRuntime();
Process pr = run.exec(s.split(" "));
pr.waitFor();
BufferedReader buf = new BufferedReader(new InputStreamReader(pr.getInputStream()));
String line = "";
while ((line=buf.readLine())!=null) {
out+=line+"\n";
}
} catch(Exception e) {
e.printStackTrace();
}
return out;
}
The command passes through:
cmd("nmap -sL -n 192.168.1.0/24 | awk '/Nmap scan report/{print $NF}'");
Expected Output:
192.168.1.0
192.168.1.1
...
Actual Output:
Starting Nmap 7.80 ( https://nmap.org ) at 2021-04-12 20:27 EET
Nmap scan report for 192.168.1.0 ...
Similar questions answers this well:
Using Java ProcessBuilder to Execute a Piped Command
Java program not getting output from terminal
To execute a pipeline, you have to invoke a shell, and then run your commands inside that shell.
Process p = new ProcessBuilder().command("bash", "-c", command).start();
bash invokes a shell to execute your command and -c means commands are read from string. So, you don't have to send the command as an array in ProcessBuilder.
Adapted to you case
String cmd(String command) {
ProcessBuilder builder = new ProcessBuilder();
builder.redirectErrorStream(true); // add stdErr to output
Process process = builder.command("bash", "-c", command).start();
StringBuilder processOutput = new StringBuilder(); // add lines easier
// try-with to auto-close resources
try (BufferedReader processOutputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));) {
String readLine;
while ((readLine = processOutputReader.readLine()) != null) {
processOutput.append(readLine + System.lineSeparator()); // use system's line-break
}
process.waitFor();
}
return processOutput.toString().trim();
}
Then call as expected:
cmd("nmap -sL -n 192.168.1.0/24 | awk '/Nmap scan report/{print $NF}'");
Note: I enhanced it a bit to
use try-with-resources to deal cleanly with resources
add output from StdErr
use a StringBuilder to concatenate output lines
use System.lineSeparator for platform-independency (Win/Mac/Linux/Unix)
Inspired by:
read the output from java exec
The pipe is interpreted by the shell. It executes one command then passes the output of one command into the next one. You could emulate this in Java starting both commands and then pumping the OutputStream of the first program to the InputStream of the second.
Alternatively if you don't want to do this you can still call something like "sh -c 'command1 | command2"

Executing custom Commands in Java

With this, we can execute "build in" commands in java. However if we want to run some custom commands from this, Changing "pwd" to "device_id -l" doesn't work. "device_id -l" should list all the ids of attached devices of currently host. if "device_id -l" is executed in terminal itself. it works fine. There is not a question for the "build in" bash commands. Thank you.
String cmd = "pwd";
Runtime run = Runtime.getRuntime();
Process pr = run.exec(cmd);
pr.waitFor();
BufferedReader buf = new BufferedReader(new InputStreamReader(pr.getInputStream()));
String line = "";
while ((line=buf.readLine())!=null)
System.out.println(line);
We can excuate
You can try using ProcessBuilder.
// create process
ProcessBuilder pb = new ProcessBuilder("/bin/bash", "-c", "device_id", "-l");
// start process
Process p = pb.start();
// wait for process exit
p.waitFor();
// read process output
BufferedReader buf = new BufferedReader(newInputStreamReader(p.getInputStream()));
String line = "";
while ((line=buf.readLine())!=null)
System.out.println(line);
You need to split your command+arguments to a String array. In your case, if you want to execute "device_id -l", split that into an array like this:
String[] cmd = new String[] {"/full/path/to/device_id", "-l"};
Process pr = Runtime.getRuntime().exec(cmd);
And, you might want to use ProcessBuilder.
String[] cmd = new String[] {"/full/path/to/device_id", "-l"};
ProcessBuilder pb = new ProcessBuilder(cmd);
Process pr = pb.start();
Finally, you'll have to take into account that Java does not look for executables in PATH (like command shell does), you'll have to provide full path to the executable/script that you want to execute (or it has to be in the working directory; you can set the working directory with ProcessBuilder.directory(File)).
See also: Difference between ProcessBuilder and Runtime.exec()

how to execute batch command (imagemagick) with java

I used the terminal command to convert all the images in the folder into RGB images using imagemagick tool
"C:\Documents and Settings\admin\My
Documents\NetBeansProjects\Archiveindexer\resources\T0003SathyabamaT\Active\CBE_2014_03_02_FE_04_MN_IMAGES_CONVERTED"
is my image folder
terminal command:
myimagefolder> mogrify -colorspace RGB *.jpg
This works fine. But when run this using java it is not working
File destpathfinalconv = new File("C:/Documents and Settings/admin/My Documents/NetBeansProjects/Archiveindexer/T0003SathyabamaT/Active/CBE_2014_03_02_FE_04_MN_IMAGES_CONVERTED");
ProcessBuilder pb = new ProcessBuilder("mogrify", "-colorspace RGB", destpathfinalconv.toString(),
"*.jpg");
pb.redirectErrorStream(true);
Process p = pb.start();
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = null;
while ((line = br.readLine()) != null) {
System.err.println(line);
}
System.err.println("Error "+p.waitFor());
System is throwing error "mogrify.exe: unrecognized option
`-colorspace RGB' # error/mogrify.c/MogrifyImageCommand/4254. Error 1"
Any idea please suggest.
You are specifying '-colorspace RGB' as a single argument, but it should be two arguments. And you should combine the path and file and search pattern into a single argument. The constructor of ProcesBuilder should be called like this:
ProcessBuilder pb = new ProcessBuilder("mogrify", "-colorspace", "RGB",
destpathfinalconv.toString() + "\\" + "*.jpg");
Try this:
ProcessBuilder pb = new ProcessBuilder(
"mogrify",
"-colorspace",
"RGB",
destpathfinalconv.toString(),
"*.jpg");
Explanation: Each String argument in the ProcessBuilder ends up as a "word" (according to shell parlance) or a separate parameter in the resulting execve call.
Combining "-colorspace RGB" results in a single parameter to mogrify, which is the (unknown) option "-colorspace\ RGB".

How to execute terminal command in specific directory from java

I am trying to execute originate command in specific directory "/usr/local/freeswitch/bin", In bin I have to run executable file fs_cli by ./fs_cli command, In fs_cli I have to execute following command
originate loopback/1234/default &bridge(sofia/internal/1789)
Its working fine from terminal, The same command can be executed from bin
./fs_cli -x "originate loopback/1234/default &bridge(sofia/internal/1789)"
I tried folowing java program to do the above task
Process pr = Runtime.getRuntime().exec("./fs_cli -x \"originate loopback/1234/default &bridge(sofia/internal/1789#192.168.0.198)\"");
BufferedReader br = new BufferedReader(new InputStreamReader(pr.getInputStream()));
String str = null;
while ((str = br.readLine()) != null) {
System.out.println(str);
}
I have creted symbolic link of fs_cli and placed in current location
The above program is showing following output
Output
-ERR "originate Command not found!
As far as I am concerned whwn above command is working fine with terminal it should be the same from java, So it shows I am wrong somewhere
Please help me to sort out this problem.
Use ProcessBuilder and supply a directory path
ProcessBuilder pb = new ProcessBuilder(
"./fs_cli",
"-x",
"originate loopback/1234/default &bridge(sofia/internal/1789#192.168.0.198)");
pb.directory(new File("..."));
Process pr = pb.start();
BufferedReader br = new BufferedReader(new InputStreamReader(pr.getInputStream()));
String str = null;
while ((str = br.readLine()) != null) {
System.out.println(str);
}
Where possible, you should provide the command arguments as separate Strings, this will pass each as a separate argument to the process and take care of those arguments that need to be escaped by quotes for you (unless it's expecting the quotes, then you should include them anyway)
The other way is:
ProcessBuilder processBuilder = new ProcessBuilder( "/bin/bash", "-c", "cd /usr/local/freeswitch/bin && ./fs_cli -x \"originate loopback/1234/default &bridge(sofia/internal/1789)\"" );
processBuilder.start();

Categories