Problems executing Bash command over SSH from Java

2019-01-28 21:54发布

问题:

I am having trouble executing commands on a remote GNU/Linux system over SSH from Java. The following commands work fine when executed in the local Bash (of course the user and host are different but the behaviour is unchanged).

$ ssh user@host.example.com 'hostname'
host
$ ssh user@host.example.com 'hostname -f'
host.example.com
$ ssh user@host.example.com "hostname -f"
host.example.com

Doing what I think is the same from Java fails for anything more complex than hostname without arguments.

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.Executor;
import org.apache.commons.exec.PumpStreamHandler;

public class SOPlayground {

    public static void main(String[] args) throws Exception {
        for (String argument : new String[]{"hostname", "'hostname'", "\"hostname\"",
            "'hostname -f'", "\"hostname -f\""}) {
            CommandLine commandLine = new CommandLine("ssh");
            commandLine.addArgument("user@host.example.com");
            commandLine.addArgument(argument);
            System.out.println(commandLine);

            final Executor executor = new DefaultExecutor();

            try (ByteArrayOutputStream os = new ByteArrayOutputStream();
                    ByteArrayOutputStream err = new ByteArrayOutputStream()) {
                executor.setStreamHandler(new PumpStreamHandler(os, err));
                int exitcode = executor.execute(commandLine);
                System.out.println("exitcode=" + exitcode);
                System.out.println(new String(os.toByteArray(), "UTF-8"));
                System.err.println(new String(err.toByteArray(), "UTF-8"));
            } catch (IOException ex) {
                System.err.println(ex.getMessage());
            }
        }
    }
}

The output is:

ssh user@host.example.com hostname
exitcode=0

host

ssh user@host.example.com 'hostname'
exitcode=0

host

ssh user@host.example.com "hostname"
exitcode=0
host


ssh user@host.example.com 'hostname -f'
Process exited with an error: 127 (Exit value: 127)
ssh user@host.example.com "hostname -f"
Process exited with an error: 127 (Exit value: 127)

As you can see, executing hostname -f over SSH from Java fails with an exit code of 127. I wonder what bash (local or remote) was unable to find what command.

I've tried to use the variant

addArgument(String argument, boolean handleQuoting)

but there was no difference in the result.

How must I build a CommandLine from Java that works over SSH?

回答1:

You can use JSch with publickey authentication.

If you only want to use exec to execute a single remote command and then closes the connection, here you have a working example:

public static void main(String[] args) {

        String user = "--";
        String host = "--";

        try
        {
            JSch jsch = new JSch();
            // key authentication
            jsch.addIdentity("id_rsa");
            // open a new session on port 22
            Session session = jsch.getSession(user, host, 22);
            session.setConfig("StrictHostKeyChecking", "no");

            session.connect();
            String command = "ls /";
            Channel channel = session.openChannel("exec");
            ((ChannelExec) channel).setCommand(command);

            channel.setInputStream(null);

            ((ChannelExec) channel).setErrStream(System.err);

            InputStream in = channel.getInputStream();

            channel.connect();
            StringBuilder sb = new StringBuilder();
            byte[] tmp = new byte[1024];
            while (true)
            {
                while (in.available() > 0)
                {
                    int i = in.read(tmp, 0, 1024);
                    if (i < 0)
                        break;
                    sb.append(new String(tmp, 0, i));
                }
                if (channel.isClosed())
                {
                    if (in.available() > 0)
                        continue;
                    System.out.println("exit-status: "
                            + channel.getExitStatus());
                    break;
                }
                try
                {
                    Thread.sleep(500);
                }
                catch (Exception ee)
                {
                }
            }
            //disconnecting and closing
            channel.disconnect();

            session.disconnect();
            System.out.println("Output: ");
            System.out.println(sb.toString());
        }
        catch (Exception e)
        {
             //something should be done here
            e.printStackTrace();

        }
    }

Output:

exit-status: 0
Output: 
1
bin
boot
cgroup
dev
etc
home
lib
lib64
lost+found
....

Hope it helps

Note: id_rsa is the path to the key file



回答2:

Thanks for the answer regarding Jsch. I've tried a different approach which writes the command to a temporary file and then executes it locally.

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.Executor;
import org.apache.commons.exec.PumpStreamHandler;
import org.apache.commons.io.IOUtils;

public class SOPlayground {

    public static void main(String[] args) throws Exception {
        final String command = "ssh user@host 'hostname -f'";
        int exitCode = executeCommand(command);
    }

    private static int executeCommand(final String command) {
        int exitcode = -1;
        File temp = null;
        try {
            temp = File.createTempFile("foo", ".tmp");
            try (OutputStream os = new FileOutputStream(temp);) {
                IOUtils.write(command, os);
            } finally {
                // os is closed
            }

            CommandLine commandLine = new CommandLine("bash");
            commandLine.addArgument(temp.getAbsolutePath());

            final Executor executor = new DefaultExecutor();
            try (ByteArrayOutputStream os = new ByteArrayOutputStream();
                    ByteArrayOutputStream err = new ByteArrayOutputStream()) {
                executor.setStreamHandler(new PumpStreamHandler(os, err));
                exitcode = executor.execute(commandLine);
                System.out.println("exitcode=" + exitcode);
                System.out.println(new String(os.toByteArray(), "UTF-8"));
                System.err.println(new String(err.toByteArray(), "UTF-8"));
            } finally {
                // os and err are closed
            }
        } catch (IOException ex) {
            System.err.println(ex.getMessage);
        } finally {
            if (temp != null) {
                temp.delete();
            }
        }
        return exitcode;
    }
}


标签: java bash ssh