首页 > 解决方案 > 使用 JSch Java 分离使用 PTY 执行的命令 (sudo) 的标准输出和错误输出

问题描述

我正在使用 Jsch,我的任务是登录服务器并运行以下命令

sudo "command"

使用以下代码,我可以成功连接并执行命令(没有错误"sudo: sorry, you must have a tty"

public String runSudo(RemoteHost remoteHost, String[] commands, OperationData operationData, String known_hosts, String id_rsa) {
    String result = "";
    Properties config = new Properties();
    config.put("StrictHostKeyChecking", "no");
    JSch jsch = new JSch();
    // Create a JSch session to connect to the server
    Session session = null;

    try {
        session = jsch.getSession(remoteHost.getUsername(), remoteHost.getHost(), remoteHost.getPort());

        if (remoteHost.getPassword() != null)
            session.setPassword(remoteHost.getPassword());
        else{
            session.setConfig("PreferredAuthentications", "publickey");
            jsch.setKnownHosts(known_hosts);
            jsch.addIdentity(id_rsa);
        }
        session.setConfig(config);
        // Establish the connection
        session.connect();
        logger.debug("Connected...");

        for (int k = 0; k < commands.length; k++) {
            ChannelExec channel = (ChannelExec) session.openChannel("exec");
            channel.setCommand(commands[k]);
            OutputStream outputStreamStdErr = new ByteArrayOutputStream();
            channel.setErrStream(outputStreamStdErr, true);
            channel.setPty(true);

            InputStream in = channel.getInputStream();
            channel.connect();
            byte[] tmp = new byte[1024];
            while (true) {
                while (in.available() > 0) {
                    int i = in.read(tmp, 0, 1024);
                    if (i < 0) {
                        break;
                    }
                    result = result + new String(tmp, 0, i);
                }
                if (channel.isClosed()) {
                    this.exitStatus += channel.getExitStatus();
                    break;
                }
                Thread.sleep(1000);
            }
            String errors = readInputStream(outputstream2inputstream(outputStreamStdErr));
            if (!errors.equals("")) {
                operationData.setErrorMessage(errors);
            }
            channel.getErrStream().close();
            channel.disconnect();
        }
        session.disconnect();
    } catch (JSchException e) {
        operationData.setErrorMessage(e.getMessage());
        logger.error(e.getMessage());
        return result;
    } catch (IOException e) {
        operationData.setErrorMessage(e.getMessage());
        logger.error(e.getMessage());
        return result;
    } catch (InterruptedException e) {
        operationData.setErrorMessage(e.getMessage());
        logger.error(e.getMessage());
        return result;
    } finally {
        return result;
    }
}

但是使用该属性channel.setPty(true);我无法读取错误流,因为它是null并且错误显示在命令的输出中。相反,如果我不使用该属性channel.setPty(true);,我可以读取错误流,但该方法返回错误“sudo:抱歉,您必须有一个 tty”,这是我想避免的。

有人可以帮助我channel.setPty(true);在同一代码中使用错误流和属性吗?

标签: javashellsshsudojsch

解决方案


这是标准的 SSH 行为(至少在 Linux 上),当启用终端仿真时,所有输出都会进入 SSH 连接中的一个流。这与您的 Java/JSch 代码无关。

不要将 PTY 用于命令自动化。PTY 旨在实现一个供人类使用的交互式终端。

您可以配置sudo为不需要 PTY。

还有其他选择。
请参阅 使用 SSH 在 Linux 上以 root 身份自动执行命令


另一种解决方法是将标准输出和标准错误重定向到服务器上的临时文件。然后将文件分别打印到终端/输出。

一个简单的实现可以是:

sudo ... > /tmp/my_stdout 2> /tmp/my_stderr ; echo "stdout:" ; cat /tmp/my_stdout ; echo "stderr:" ; cat /tmp/my_stderr

推荐阅读