I tried to piping password to smbpassword via Java application, here how I pipe via terminal:
(echo newPassword; echo confirmPassword) | smbpasswd -a -s client1
and the output show the command is nicely done:
Added user client1
However, I cannot accomplish this via Java application, here the codes that I use:
public void run(String command, String[] prompt) {
try {
String[] args = new String[] {"/bin/bash","-c","echo " + rootPassword + "| sudo -S " + command};
Process proc = new ProcessBuilder(args).start();
if (prompt != null && prompt.length > 0) {
for (int i = 0; i < prompt.length; i++) {
proc.getOutputStream().write((prompt[i] + "\r\n").getBytes());
proc.getOutputStream().flush();
}
}
proc.waitFor();
String output = "";
String line;
BufferedReader input = new BufferedReader(new InputStreamReader(proc.getInputStream()));
while ((line = input.readLine()) != null) {
System.out.println(line);
output += line + "\n";
}
System.out.println(output);
input.close();
}
catch (Exception ex) {
System.out.println(null, ex.getMessage());
}
}
and I tried to use OutputStream it like:
String[] pipe = { password, password };
run("smbpasswd -s -a " + username, pipe);
or pipe it like:
run("(echo " + password + "; echo " + password + ") | smbpasswd -s -a " + username, null);
but both doesn't work and I got no output and the user seems not created after I check via pdbedit -L. However, I able to execute another command with pipe such as echo username:password | chpasswd via that function.
Any idea?
Thanks in advance.
The main conceptual problem is that shell code such as you are trying to run:
(echo newPassword; echo confirmPassword) | smbpasswd -a -s client1
is not a "command" in the sense that sudo requires. In fact, it's not a "command" even in shell terminology; rather, it's a pipeline, consisting of a compound command and a simple command. sudo expects you to provide a simple command.
But that's actually a bit of an aside. Your problem isn't really specific to sudo; rather, it's a shell problem. In the failure case, your run() method invokes bash to execute the following shell code:
echo rootPassword | sudo -S (echo newPassword; echo confirmPassword) | smbpasswd -a -s client1
That's problematic for at least two reasons:
A subshell invocation cannot appear in a simple command's (i.e. sudo's) argument list like that. You could put a command substitution there ($(echo ...)), but that will not serve your purpose because the internal line break will not then be preserved.
Even if you solved (1), your second pipe is at the wrong level: it would pipe the output of sudo into smbpasswd. That's fine data-wise, but it leaves smbpasswd running without privilege -- the sudo does not apply to it.
You could probably solve the problem by instructing sudo to run the code via a shell. You could do that by embedding the ultimate command inside an inner bash -c command, but it would probably be easier to use sudo's -s option (but beware that -s can choose a different shell than bash if you let it do). Something along those lines is probably the most general solution if you want to encode the whole operation in a single String in your Java source:
String[] args = new String[] { "/bin/bash", "-c",
"echo " + rootPassword + " | sudo -S -s '" + command + "'" };
String[] args = new String[] { "/bin/bash", "-c",
"echo " + rootPassword + " | sudo -S bash -c '" + command + "'" };
Much of your problem, however, arises from your reliance on echo to feed data to your commands. You should consider instead using your Process's OutputStream to feed data to it from your Java program. That does not lend itself to quite as simple a Java-side API, but I think you're fooling yourself a bit about how simple your API really is. It has hidden gotchas, as you discovered, and even the revision I offered above still has at least one: it will likely break if the command submitted to it contains a single-quote character (').
Update:
Here's one way to feed the process the needed data via its OutputStream:
public void run2(String command, String commandInput)
throws IOException, InterruptedException {
String[] args = new String[] { "sudo", "-S", "bash", "-c", command };
Process proc = new ProcessBuilder(args).start();
Writer toProc = new OutputStreamWriter(proc.getOutputStream());
toProc.write(rootPassword, 0, rootPassword.length());
toProc.write('\n');
toProc.write(commandInput, 0, commandInput.length());
toProc.close();
proc.waitFor();
BufferedReader input = new BufferedReader(new InputStreamReader(proc.getInputStream()));
StringBuilder output = new StringBuilder();
String line;
while ((line = input.readLine()) != null) {
output.append(line).append('\n');
}
System.out.println("Command output:");
System.out.println(output.toString());
input.close();
}
You might use that from within its class as
run2("smbpasswd -a -s client1", "newPassword\nnewPassword");
Note in particular that the Process's output stream must, in general, be closed after you've written everything you intend to write to it. Some commands you could run will not exit if you do not do so.
Note also
For the general case, you must read every Process's input stream and error stream (or just the former if you combine them), in parallel with the process itself. If you have not combined the streams then you must read them in parallel with each other. If you are writing to the process's output stream then that, too, must be in parallel. If you do not arrange to handle each stream via its own thread then it is possible for the process to get stuck.
On the other hand, you don't need a separate thread to run the Process itself in -- after all, it represents an entire separate process. Just defer waiting for it until you have closed its output stream and read the end of its input and error streams.
Related
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.
Consider the following code:
String commandf = "ls /etc | grep release";
try {
// Execute the command and wait for it to complete
Process child = Runtime.getRuntime().exec(commandf);
child.waitFor();
// Print the first 16 bytes of its output
InputStream i = child.getInputStream();
byte[] b = new byte[16];
i.read(b, 0, b.length);
System.out.println(new String(b));
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
The program's output is:
/etc:
adduser.co
When I run from the shell, of course, it works as expected:
poundifdef#parker:~/rabbit_test$ ls /etc | grep release
lsb-release
The internets tell me that, due to the fact that pipe behavior isn't cross-platform, the brilliant minds who work in the Java factory producing Java can't guarantee that pipes work.
How can I do this?
I am not going to do all of my parsing using Java constructs rather than grep and sed, because if I want to change the language, I'll be forced to re-write my parsing code in that language, which is totally a no-go.
How can I make Java do piping and redirection when calling shell commands?
Write a script, and execute the script instead of separate commands.
Pipe is a part of the shell, so you can also do something like this:
String[] cmd = {
"/bin/sh",
"-c",
"ls /etc | grep release"
};
Process p = Runtime.getRuntime().exec(cmd);
I ran into a similar problem in Linux, except it was "ps -ef | grep someprocess".
At least with "ls" you have a language-independent (albeit slower) Java replacement. Eg.:
File f = new File("C:\\");
String[] files = f.listFiles(new File("/home/tihamer"));
for (String file : files) {
if (file.matches(.*some.*)) { System.out.println(file); }
}
With "ps", it's a bit harder, because Java doesn't seem to have an API for it.
I've heard that Sigar might be able to help us:
https://support.hyperic.com/display/SIGAR/Home
The simplest solution, however, (as pointed out by Kaj) is to execute the piped command as a string array. Here is the full code:
try {
String line;
String[] cmd = { "/bin/sh", "-c", "ps -ef | grep export" };
Process p = Runtime.getRuntime().exec(cmd);
BufferedReader in =
new BufferedReader(new InputStreamReader(p.getInputStream()));
while ((line = in.readLine()) != null) {
System.out.println(line);
}
in.close();
} catch (Exception ex) {
ex.printStackTrace();
}
As to why the String array works with pipe, while a single string does not... it's one of the mysteries of the universe (especially if you haven't read the source code). I suspect that it's because when exec is given a single string, it parses it first (in a way that we don't like). In contrast, when exec is given a string array, it simply passes it on to the operating system without parsing it.
Actually, if we take time out of busy day and look at the source code
(at http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/Runtime.java#Runtime.exec%28java.lang.String%2Cjava.lang.String[]%2Cjava.io.File%29), we find that is exactly what is happening:
public Process [More ...] exec(String command, String[] envp, File dir)
throws IOException {
if (command.length() == 0)
throw new IllegalArgumentException("Empty command");
StringTokenizer st = new StringTokenizer(command);
String[] cmdarray = new String[st.countTokens()];
for (int i = 0; st.hasMoreTokens(); i++)
cmdarray[i] = st.nextToken();
return exec(cmdarray, envp, dir);
}
Create a Runtime to run each of the process. Get the OutputStream from the first Runtime and copy it into the InputStream from the second one.
#Kaj accepted answer is for linux. This is the equivalent one for Windows:
String[] cmd = {
"cmd",
"/C",
"dir /B | findstr /R /C:"release""
};
Process p = Runtime.getRuntime().exec(cmd);
I am trying to install missing dependencies on a Linux for a program I am making. I however am failing at getting the root access required to install the missing dependencies. Here is what I have so far:
My logic is as follows:
1) Check if the dependency is installed using pacapt (npm in this case)
2) if so then get the user password using a text prompt
3) then continue further instructions like so: echo [userpass] | sudo -S ...
Right now the 'echo [userpass] | sudo -S ...' command gets printed out to the shell like so; [userpass] | sudo -S ... (where the user password is displayed in place of [userpass]), but does not execute.
And here is my code:
public class LinuxDependencyCheck extends Application{
public static void main (String [] args){
launch(args);
}
#Override
public void start(Stage mainWindow){
String userPass = null;
String terminalOut = null;
terminalOut = runBash("./LinuxScripts/pacapt -Qqe npm");
if (terminalOut.equals("npm")){
userPass = getUserPass();
if (userPass != null){
System.out.println("runing");
runBash("echo " + userPass + " | sudo -S npm install" +
" phantomjs2");
}
}
}
public String runBash(String runCommand){
String result = null;
String returnVal = null;
try {
Runtime r = Runtime.getRuntime();
Process p = r.exec(runCommand);
BufferedReader in =
new BufferedReader(new InputStreamReader(p.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println(inputLine);
result += inputLine;
returnVal = inputLine;
}
in.close();
} catch (IOException e) {
System.out.println(e);
}
return returnVal;
}
public String getUserPass(){
TextInputDialog dialog = new TextInputDialog("Password");
dialog.setTitle("Installation helper");
dialog.setHeaderText("It looks like you are missing" +
" dependecies to complete this action" +
" would you like to try to install" +
" them now");
dialog.setContentText("Please enter your password :");
// Traditional way to get the response value.
Optional<String> result = dialog.showAndWait();
if (result.isPresent()){
return result.get().toString();
}
return result.get();
}
}
Your runBash() method is poorly named, as it does nothing to cause the given command to be run via bash. It is therefore also inappropriate for use with a command string such as you are specifying, which relies on the shell's pipe operator to string two separate commands together.
When you do this ...
runBash("echo " + userPass + " | sudo -S npm install" +
" phantomjs2");
... Java splits the string on whitespace, takes the first substring ("echo") as the command, and executes that command with all the other substrings strings as arguments. Needless to say, that will run without error, but also without the effect you intended.
If you really want to execute the command string via bash (as it appears you do), then in your runBash() method you could change this ...
Process p = r.exec(runCommand);
... to this ...
Process p = r.exec("/bin/bash", "-c", runCommand);
. That should at least get you past your first hurdle.
You also should close the Process's OutputStream (by which you could have piped data into the process), and drain the Process's error stream. In general, you need to drain the input and error streams in parallel, because if either one's buffer fills up then the process can block. Perhaps that's not a risk for this particular command, but you'll need to judge. It's also good form to waitFor() the Process; doing so may avoid Java accumulating zombie child processes.
I'm executing some commands from the command line in my java program, and it appears that it doesn't allow me to use "grep"? I've tested this by removing the "grep" portion and the command runs just fine!
My code that DOESN'T work:
String serviceL = "someService";
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("chkconfig --list | grep " + serviceL);
Code that does work:
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("chkconfig --list");
Why is this? And is there some sort of correct method or workaround? I'm aware that I could just parse the entire output, but I would find it easier to do it all from the command line. Thanks.
The pipe (like redirection, or >) is a function of the shell, and so executing it directly from Java won't work. You need to do something like:
/bin/sh -c "your | piped | commands | here"
which executes a shell process within the command line (including pipes) specified after the -c (in quotes).
So, here's is a sample code that works on my Linux OS.
public static void main(String[] args) throws IOException {
Runtime rt = Runtime.getRuntime();
String[] cmd = { "/bin/sh", "-c", "ps aux | grep skype" };
Process proc = rt.exec(cmd);
BufferedReader is = new BufferedReader(new InputStreamReader(proc.getInputStream()));
String line;
while ((line = is.readLine()) != null) {
System.out.println(line);
}
}
Here, I'm extracting all my 'Skype' processes and print the content of the process input stream.
You're trying to use piping which is a function of the shell ... and you're not using a shell; you're exec'ing the chkconfig process directly.
The easy solution would be to exec the shell and have it do everything:
Process proc = rt.exec("/bin/sh -c chkconfig --list | grep " + serviceL);
That being said ... why are you piping to grep? Just read the output of chkconfig and do the matching yourself in java.
String[] commands = { "bash", "-c", "chkconfig --list | grep " + serviceL };
Process p = Runtime.getRuntime().exec(commands);
or if you are in a linux env just use grep4j
I am trying to print my mac's [edit: Apple computer] serial number in a java program. I am familiar with the Unix command
ioreg -l | awk '/IOPlatformSerialNumber/ { print $4;}'
which accomplishes this task in terminal.
When I try
String command = "ioreg -l | awk '/IOPlatformSerialNumber/ { print $4; }'"
Runtime terminal = Runtime.getRuntime();
String input = new BufferedReader(
new InputStreamReader(
terminal.exec(commands).getInputStream())).readLine();
System.out.println(new BufferedReader(
new InputStreamReader(
terminal.exec(command, args).getInputStream())).readLine());
my serial number is not printed. Instead it prints:
<+-o Root class IORegistryEntry, id 0x100000100, retain 10>
I think the problem is that terminal.exec() is not meant to take the whole command string. Is there something in java similar to the argument shell = True in python's Popen(command, stdout=PIPE, shell=True) that will allow me to pass the whole command string?
I see two possibilities:
Parse the output of ioreg -l using, say, Scanner.
Wrap the command in a shell script and exec() it:
#!/bin/sh
ioreg -l | awk '/IOPlatformSerialNumber/ { print $4;}'
Addendum: As an example of using ProcessBuilder, and incorporating a helpful suggestion by Paul Cager, here's a third alternative:
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class PBTest {
public static void main(String[] args) {
ProcessBuilder pb = new ProcessBuilder("bash", "-c",
"ioreg -l | awk '/IOPlatformSerialNumber/ { print $4;}'");
pb.redirectErrorStream(true);
try {
Process p = pb.start();
String s;
// read from the process's combined stdout & stderr
BufferedReader stdout = new BufferedReader(
new InputStreamReader(p.getInputStream()));
while ((s = stdout.readLine()) != null) {
System.out.println(s);
}
System.out.println("Exit value: " + p.waitFor());
p.getInputStream().close();
p.getOutputStream().close();
p.getErrorStream().close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Pipes aren't supported by Runtime.exec(..) since they are a feature of shells. Instead, you'd have to emulate the pipe yourself, e.g.
String ioreg = toString(Runtime.exec("ioreg -l ").getInputStream());
Process awk = Runtime.exec("awk '/IOPlatformSerialNumber/ { print $4;}'");
write(awk.getOutputStream(), ioreg);
String input = new BufferedReader(new InputStreamReader(awk.getInputStream())).readLine();
Alternatively, you could of course run a shell as a process, e.g. Runtime.exec("bash"), and interact with it by reading and writing its IO streams. Interacting with processes is a bit tricky though and has some gotchas and let it execute your command (see comments)
To get the MAC addres via Java you can use java.net.NetworkInterface:
NetworkInterface.getByName("xxx").getHardwareAddress()
If you don't know the name (I assume it to be 'eth0' on linux) of your network interface, you can even iterate throug all of your network interfaces using NetworkInterface.getNetworkInterfaces().