Java: Convert command array to string that can execute in bash - java

I have a command in array format that I can pass to execve(). For example, the command is:
echo "It's Nice"
and the array I have is ["echo","It's Nice"]. I'm trying to convert this array into a string that I can write in bash and execute properly. I obviously cannot join on this array with space delimiter because I will get: echo It's Nice which cannot be run since it has an unterminated single quote.
Is there a BKM to convert this to a runnable string? maybe a library that does that already in Java? It can get tricky when the command has many special characters that should be escaped\quoted in order to run properly.
EDIT:
I would like to make my question clearer. The user gives me his command as a string array, I execute it and everything works fine. Now I need to report to the user what I have ran. I do not want to show the command as an array, instead I would like to show it as a string that the user can simply copy and paste to his bash shell and execute it if he wants to. So my input is [echo, It's Nice] and my output should be echo "It's Nice". It seems like a simple function to write, but i'm not sure i'm thinking of all the end-cases here (like if the string has a quote or some other special character the shell manipulates). I was wondering maybe there's some code that already does that and covers the end cases i'm yet to think about.

You don't need to convert array to string, you can directly execute a command using ProcessBuilder:
String runShell(final String[] commandArgs) {
try {
final ProcessBuilder processBuilder = new ProcessBuilder(commandArgs);
processBuilder.redirectErrorStream(true); // merge stderr with stdout
Process process = processBuilder.start();
ret = process.waitFor();
BufferedReader br = new BufferedReader(new InputStreamReader(
process.getInputStream() ));
br.lines().forEach(System.out::println); // print stdout + stderr
process.destroy();
br.close();
}
catch (IOException|InterruptedException e) {
e.printStackTrace();
}
return commandArgs[0] + Arrays.asList(commandArgs).stream()
.skip(1)
.collect(Collectors.joining("\" \"", " \"", "\""));
}
and call it as:
runShell(new String[] {"pwd"}); // print current working directory
runShell(new String[] {"ls", "-l"}); // list all the files and directories
runShell(new String[] {"echo", "It's Nice"}); // echo something

That's easy to do in Java 8:
String joined = String.join(" ", iteratable);

Related

java get output of batch file command by command

I need to execute .bat files in my java application. Suppose I have a text file with this sample content:
{
"id": 12,
"name": "test"
}
And in my .bat file, I have a command for outputing text file content. So this is my .bat file content:
#some other commands
more path\to\file.txt
And finally, this is my java code for executing this .bat file:
Process process = Runtime.getRuntime().exec("path\to\file.bat");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
List<String> outputs = new ArrayList<>();
while ((line = reader.readLine()) != null) {
outputs.add(line);
}
After executing this code, the outputs list has data something like this:
[...,
"{",
" "id": 12",",
" "name": "test",",
"}"
]
I means, this returns output line by line. But I want to have whole command output as one index of my list. In the other words, I want to have command by command instead of line by line output(every command has just one output).
Is this possible doing something like that?
Edit: I tried using ProcessBuilder also, but result was the same.
You claim
And in my .bat file, I have a command for outputing text file content.
and
I want to have command by command instead of line by line output(every command has just one output).
If I'm understanding this correctly, that means that you run your code only once (one "command") every time that you want to output a file. That is, you're only requiring that the outputs described are joined together in a single line, at which point you can put the lines in a list. This can be achieved like so:
Process process = Runtime.getRuntime().exec("path\to\file.bat");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder builder = new StringBuilder();
List<String> outputs = new ArrayList<>();
//if desired, append prefix
//builder.append("File: {");
while ((line = reader.readLine()) != null) {
builder.append(line);
//if desired, append delimiter
//builder.append("\n");
}
//if desired, append suffix
//builder.append("}");
String concatenatedString = builder.toString();
Alternatively, in Java 8+, you can do the following (and even specify details of how lines are joined together):
Process process = Runtime.getRuntime().exec("path\to\file.bat");
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String concatenatedString = reader.lines().collect(Collectors.joining(" "));
Naturally, I'm assuming that you're just using the example of reading files as a proxy for another where you must actually read a process' output. If all you require is a file read, a Process is not required to achieve this. Instead, you can get file contents as so:
String concatenatedString = "";
try (Stream<String> stream = Files.lines(Paths.get("path\to\text\file.txt"))) {
concatenatedString = stream.collect(Collectors.joining(" "));
}
//catch...
Now, if you actually want to join all text output together from many processes, but only have it as an aggregate (i.e. you can't join process outputs one by one, then store them), you're going to end up having to do the following:
Join all the strings:
This is easily doable using StringBuffer append or Collectors join as shown above.
Split them apart at the right places:
You will have to identify some marker of the separations between the relevant process outputs (for example, the text of the commands, or maybe the character at the end of the prompt). After identifying the marker(s), you'll have to write a regular expression or parser to separate out the relevant parts of your input, using methods like String substring or StringBuffer substring. If you use regular expressions to match the markers in your input to capturing groups, you can use region, start, and end to greatly simplify the process of splitting up your input.
As #Mofi and #kriegaex stated you should explain the use of batch files. I suppose that you already have them (batch files or some other executables) and you can not get rid of them but instead want to execute them and capture stdout of each execution into a single List or Map item.
Your current implementation appends each line into List:
while ((line = reader.readLine()) != null) {
outputs.add(line);
}
One solution is to use StringBuilder to concatenate stdout lines of each executable. After that each concatenated output is appended into Map. See this sample:
// Create Map for outpus.
Map<String, String> outputs = new HashMap<String, String>();
// Loop through batch files.
for (String bat : new String[] { "file12.bat", "file13.bat" }) {
// Execute batch.
Process process = Runtime.getRuntime().exec(bat);
// Open Reader...
BufferedReader reader =
new BufferedReader(new InputStreamReader(process.getInputStream()));
// ... and read contents into StringBuilder.
StringBuilder contents = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
contents.append(line);
contents.append("\n"); // Append newline because reader.readLine() omits it.
}
// Close Reader.
reader.close();
// Add contents into Map.
outputs.put(bat, contents.toString());
}
After that you can verify Map contents for example like this:
for (String bat : outputs.keySet()) {
System.out.print("### output of ");
System.out.print(bat);
System.out.println(" ###");
System.out.println(outputs.get(bat));
}
It looks as if you do not want to perform a System.out.println() and instead collect all the output of a command and print it in bulk at the end of each command.
Well, then, write your own CommandResultCollector type where you initiate a StringBuffer and concatenate strings with proper line breaks as part of a single command execution and at the end of the command execution, convert it to a String and print the whole thing.
Alternatively, you can create an ArrayList and add all the Strings that are being printed as part of the command and iterate at the end of the execution to print them all at the end of every command execution.
I am sure there are better solutions that use the Heap memory intelligently. You can solve this problem in many ways, choose the simplest and least resource intensive one.

Windows - Using set and echo in Java program

I want to set and echo a Windows variable in Java:
public static void main(String[] args) throws IOException
{
Runtime rt = Runtime.getRuntime();
String[] cmd = { "cmd.exe", "/c", "set HOSTNAME=%COMPUTERNAME% "
+ "&& echo %HOSTNAME%" };
Process proc = rt.exec(cmd);
BufferedReader stdInput = new BufferedReader(
new InputStreamReader(proc.getInputStream()));
BufferedReader stdError = new BufferedReader(
new InputStreamReader(proc.getErrorStream()));
System.out.println("Output:\n");
String s = null;
while ((s = stdInput.readLine()) != null)
{
System.out.println(s);
}
System.out.println("Error (if any):\n");
while ((s = stdError.readLine()) != null)
{
System.out.println(s);
}
}
I expect the program will print out my computer host name or I will use this value for another purpose. But the output is just like this:
Output:
%HOSTNAME%
Error (if any):
How could I get the value that I have set in the command set HOSTNAME=%COMPUTERNAME%
It's irrelevant to Java because it's how cmd parses the command. The whole command will be parsed at once for variable expansion. At the time the command is parsed the variable is not yet available, so it'll be replaced with nothing in a batch file or leave as-is in command line
You need to use delayed expansion and print the variable with !!
cmd.exe /V:ON /c set HOSTNAME=%COMPUTERNAME% && echo !HOSTNAME!
The /V:ON is for enabling delayed expansion
CMD [/A | /U] [/Q] [/D] [/E:ON | /E:OFF] [/F:ON | /F:OFF] [/V:ON | /V:OFF]
[[/S] [/C | /K] string]
...
/V:ON Enable delayed environment variable expansion using ! as the
delimiter. For example, /V:ON would allow !var! to expand the
variable var at execution time. The var syntax expands variables
at input time, which is quite a different thing when inside of a FOR
loop.
In a batch file it can be enabled by setlocal EnableDelayedExpansion
However for that purpose just cmd.exe /V:ON /c echo %COMPUTERNAME% is enough. Yet it's still not the efficient way. There are better ways to get hostname in Java
Map<String, String> env = System.getenv();
if (env.containsKey("COMPUTERNAME"))
return env.get("COMPUTERNAME");
or
InetAddress.getLocalHost().getHostName()
Your syntax for running two commands at once is wrong. Try using a single & in the command line instead of &&.
The real problem, I think, is that cmd.exe does all variable substitution before executing the command line (including parsing the &&). When it finds the syntax %HOSTNAME% for a variable that doesn't exist (yet), it leaves the text as is: %HOSTNAME%. So try issuing two commands to the same process, followed by an exit command.
Another approach is to change the command to:
set HOSTNAME=%COMPUTERNAME% & SET HOSTNAME
Then you will get back the string "HOSTNAME=my_computer_name", from which you can strip out the leading "HOSTNAME=" prefix.

java.lang.Runtime exception "Cannot run program"

I am getting an exception like java.io.IOException: Cannot run program cat /home/talha/* | grep -c TEXT_TO_SEARCH": error=2, No such file or directory while executing the command below despite that there are no issues when I execute the same command through the terminal. I need to execute and return the output of the command below:
cat /home/talha/* | grep -c TEXT_TO_SEARCH
Here is the method used to execute commands using Runtime class:
public static String executeCommand(String command) {
StringBuffer output = new StringBuffer();
Process p;
try {
p = Runtime.getRuntime().exec(command);
p.waitFor();
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = "";
while ((line = reader.readLine()) != null) {
output.append(line + "\n");
}
} catch (Exception e) {
e.printStackTrace();
}
return output.toString();
}
Runtime.exec does not use a shell (like, say, /bin/bash); it passes the command directly to the operating system. This means wildcards like * and pipes (|) will not be understood, since cat (like all Unix commands) does not do any parsing of those characters. You need to use something like
p = new ProcessBuilder("bash", "-c", command).start();
or, if for some bizarre reason you need to stick to using the obsolete Runtime.exec methods:
p = Runtime.getRuntime().exec(new String[] { "bash", "-c", command });
If you are only running that cat/grep command, you should consider abandoning the use of an external process, since Java code can easily traverse a directory, read lines from each file, and match them against a regular expression:
Pattern pattern = Pattern.compile("TEXT_TO_SEARCH");
Charset charset = Charset.defaultCharset();
long count = 0;
try (DirectoryStream<Path> dir =
Files.newDirectoryStream(Paths.get("/home/talha"))) {
for (Path file : dir) {
count += Files.lines(file, charset).filter(pattern.asPredicate()).count();
}
}
Update: To recursively read all files in a tree, use Files.walk:
try (Stream<Path> tree =
Files.walk(Paths.get("/home/talha")).filter(Files::isReadable)) {
Iterator<Path> i = tree.iterator();
while (i.hasNext()) {
Path file = i.next();
try (Stream<String> lines = Files.lines(file, charset)) {
count += lines.filter(pattern.asPredicate()).count();
}
};
}
$PATH is an environment variable that tells the system where to search for executable programs (it's a list of directories separated by colons). It is usually set in your .bashrc or .cshrc file but this is only loaded when you log in. When Java runs, $PATH is likely not set because the rc file is not executed automatically, so the system can't find programs without specifying exactly where they are. Try using /bin/cat or /usr/bin/cat instead of just cat and see if it works. If it does, $PATH is your problem. You can add $PATH=/bin:/usr/bin to your script or just leave it with the directory name specified (e.g. /bin/cat).
Just because you can execute it in a login session doesn't mean it will work the same when a daemon like your Java program runs. You have to know what's in your .bashrc or .cshrc file and even sometimes how the system file is written (/etc/bashrc) in order to know how to write a script that runs under a daemon. Another consideration is that daemons often run under the context of a different user, and that throws things off, too.

Passing parameter with space to Shell Script using Java

I've had trouble passing parameters with spaces to a shell script with the following JAVA code:
List<String> parameterList = new ArrayList<String>();
parameterList.add(executable);
parameterList.add(inputFile);
parameterList.add(outputPath);
ProcessBuilder pb = new ProcessBuilder(parameterList);
pb.redirectErrorStream();
Process p = pb.start();
BufferedReader reader = new BufferedReader(newInputStreamReader(p.getInputStream()));
#SuppressWarnings("unused")
String line = null;
while ((line = reader.readLine()) != null) {
}
The problem comes when passing my parameter to the 2nd add to the list in the input file variable.
If I use my bat file, I can get the string with spaces, say "my data.xml" using the variable %~1. However, in LINUX, I cannot use %~1 instead there is "$1" which does not accept strings with spaces. Is there a similar approach I can use that will allow me to pass parameters with spaces to a shell script?
EDIT:
In the console the command should go like this:
run.sh "/scratch/input/my data.xml" /scratch/output/
Is there a way to have the above command be called from JAVA verbatim?
Thanks!

Not getting complete output java exec

I am running the following using Apache exec lib.
wmic LOGICALDISK GET Name,ProviderName /FORMAT
This command returns a listing of all the mapped drives and their mapping. When I run it from the command line, it works great. When I run it from within java, it returns the first 2 drives and 1 of the drives listed in the middle.
I've piped the stream to stdout, used stream gobbler, etc. I have several other commands I run that work fine and I read the streams without problem. I'm stumped. Any ideas? Encoding possibly? I've never had this problem before.
Oh, I've also run with ProcessBuilder, Runtime.exec, and DefaultExecutor. Same results throughout.
Thanks.
stdbuf -o0 wmic LOGICALDISK GET Name,ProviderName /FORMAT | cat
download stdbuf from: http://www.inreto.de/ffp/0.7/arm/packages/coreutils-8.14-arm-1.txz
or maybe:
public static String execCmd(String cmd) throws java.io.IOException {
Process proc = Runtime.getRuntime().exec(cmd);
java.io.InputStream is = proc.getInputStream();
java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
String val = "";
if (s.hasNext()) {
val = s.next();
}
else {
val = "";
}
return val;
}
from:https://stackoverflow.com/a/20624914/264181

Categories