15

(这是我的同事在其他地方发布的问题,但我想我会在这里发布,看看是否可以吸引不同的受众。)

大家好,我正在测试编写一个小型 Java 应用程序的可能性,该应用程序将使用 Psexec 来启动远程作业。在测试将 java 程序的标准输入和标准输出绑定到 psexec 的过程中,我遇到了一个奇怪的错误。

我的测试程序是一个基本的回声程序。它启动一个线程从标准输入读取,然后将读取的输出直接通过管道传输回标准输出。当在本地机器上运行而不是从 psexec 上运行时,它运行良好。正如它应该的那样。

但是,当我第一次从 PsExec 调用它时,输入直接通过管道传输到标准输出,它会丢失。使该错误真正奇怪的是,只有第一次将输入直接通过管道传输到 stdout 时才会丢失。如果输入字符串附加到另一个字符串,它工作正常。字符串文字或字符串变量。但是,如果输入字符串直接发送到标准输出,它不会通过。第二次将其发送到标准输出时,它会正常运行 - 之后每次都可以。

我完全不知道这里发生了什么。我试图测试我能想到的每一个可能的错误。我没主意了。我错过了一个还是这只是 psexec 内部的东西?

这是有问题的代码,它位于三个类中(其中一个实现了一个接口,它是一个单一的函数接口)。

主类:

public class Main {
    public static void main(String[] args) {
        System.out.println("Starting up.");

        CReader input = new CReader(new BufferedReader(
            new InputStreamReader(System.in)));
        CEcho echo = new CEcho();

        input.addInputStreamListener(echo);
        input.start();

        System.out.println("Successfully started up.  Awaiting input.");
    }
}

CReader 类是从标准输入读取的线程:

public class CReader extends Thread {
    private ArrayList<InputStreamListener> listeners = 
        new ArrayList<InputStreamListener>();
    private boolean exit = false;
    private Reader in;

    public CReader(Reader in) {
        this.in = in;
    }

    public void addInputStreamListener(InputStreamListener listener) {
        listeners.add(listener);
    }

    public void fireInputRecieved(String input) {

        if(input.equals("quit"))
        exit = true;

        System.out.println("Input string has made it to fireInputRecieved: "
            + input);

        for(int index = 0; index < listeners.size(); index++)
            listeners.get(index).inputRecieved(input);
    }

    @Override
    public void run() {

        StringBuilder sb = new StringBuilder();
        int current = 0, last = 0;

        while (!exit) {
            try {
                current = in.read();
            }
            catch (IOException e) {
                System.out.println("Encountered IOException.");
            }

            if (current == -1) {
                break;
            }

            else if (current == (int) '\r') {
                if(sb.toString().length() == 0) {
                    // Extra \r, don't return empty string.
                    continue;
                }
                fireInputRecieved(new String(sb.toString()));
                sb = new StringBuilder();
            }

            else if(current == (int) '\n') {
                if(sb.toString().length() == 0) {
                    // Extra \n, don't return empty string.
                    continue;
                }
                fireInputRecieved(new String(sb.toString()));
                sb = new StringBuilder();
            }
            else {
                System.out.println("Recieved character: " + (char)current);
                sb.append((char) current);
                last = current;
            }
        }       
    }
}

CEcho 类,该类通过管道将其返回到标准输出:

public class CEcho implements InputStreamListener {
    public void inputRecieved(String input) {
        System.out.println("\n\nSTART INPUT RECIEVED");
        System.out.println("The input that has been recieved is: "+input);
        System.out.println("It is a String, that has been copied from a " +
            "StringBuilder's toString().");
        System.out.println("Outputting it cleanly to standard out: ");
        System.out.println(input);
        System.out.println("Outputting it cleanly to standard out again: ");
        System.out.println(input);
        System.out.println("Finished example outputs of input: "+input);
        System.out.println("END INPUT RECIEVED\n\n");
    }
}

最后,这是程序输出:

>psexec \\remotecomputer "C:\Program Files\Java\jre1.6.0_05\bin\java.exe" -jar "C:\Documents and Settings\testProram.jar"

PsExec v1.96 - 远程执行进程
版权所有 (C) 2001-2009 Mark Russinovich
Sysinternals - www.sysinternals.com


启动。
成功启动。等待输入。
测试
收到字符:T
收到的字符:e
收到的字符:s
收到的字符:t
输入字符串已进入 fireInputRecieved: Test


收到的开始输入
收到的输入是:测试
它是一个字符串,从 StringBuilder 的 toString() 复制而来。
将其干净地输出到标准输出:

再次将其干净地输出到标准输出:
测试
输入的完成示例输出:测试
收到的最终输入
4

13 回答 13

4

您是否尝试过将输出重定向到文件( java...>c:\output.txt )?这样,您可以仔细检查所有内容是否都进入标准输出,并且可能只是被 psexec 吃掉

于 2009-08-24T22:52:31.523 回答
3

PsExec 正在吃掉输出。下一个有趣的事情可能是吃掉输出的地方。您可以通过获取Wireshark的副本并检查有问题的输出是否正在遍历网络来检查这一点。如果不是,那么它正在远程被吃掉。如果是的话,它正在当地被吃掉。

并不是说我真的很确定从那里去哪里,但收集更多信息似乎是一条不错的道路……

于 2009-08-28T13:51:34.470 回答
2

我遇到了同样的问题并尝试了多种重定向组合。这是有效的:

processBuilder.redirectErrorStream(true);
processBuilder.redirectOutput(Redirect.PIPE);
processBuilder.redirectInput(Redirect.INHERIT);

final Process process = processBuilder.start();

// Using Apache Commons IOUtils to get output in String
StringWriter writer = new StringWriter();
IOUtils.copy(process.getInputStream(), writer, StandardCharsets.UTF_8);
String result = writer.toString();
logger.info(result);

final int exitStatus = process.waitFor();

processBuilder.redirectInput 的 Redirect.INHERIT 让我丢失了远程命令输出。

于 2016-01-19T20:43:42.367 回答
1

System.out没有配置为自动刷新?第一次打印后,尝试System.out.flush()查看第一行是否出现而没有打印更多行。

(哦,是的,说真的,它是“收到”,而不是“收到”。)

于 2009-08-19T18:45:54.337 回答
1

好的,我周末一直在考虑这个问题,自从您从一台机器跳到另一台机器以来,我想知道是否可能存在 CharSet 问题?也许它是第一次吃字符串并处理不同的代码页或字符集问题?Java 通常是 16 位字符,而现在 Windows 是带有代码页的 8 位或 utf-8。

本地和远程机器有不同的默认字符集吗?如果您通过网络发送本地化数据,它可能行为不端。

于 2009-08-24T18:12:07.830 回答
1

我在运行 psexec 时看到的是它会生成一个子窗口来完成这项工作,但不会将该程序的输出返回到它的控制台窗口。我建议使用 WMI 或某种形式的 Windows 进程 API 框架来获得 psexec 似乎缺乏的控制级别。当然 java 有一个等价于 .Net 的 System.Diagnotics.Process 类。

于 2009-08-26T06:04:36.750 回答
1

也许您可以尝试将输入副本传递给您的听众:

 public void fireInputRecieved(String input) {

    if(input.equals("quit"))
    exit = true;

    String inputCopy = new String(input);
    System.out.println("Input string has made it to fireInputRecieved: "
        + input);



    for(int index = 0; index < listeners.size(); index++)
        listeners.get(index).inputRecieved(inputCopy);
}

我对侦听器有类似的问题,其中传递的变量最终会为空,除非我确实传递了它的显式副本。

于 2009-08-26T08:59:03.700 回答
1

我不一定有答案,但有些评论可能会有所帮助。

  • “传递副本”的想法无关紧要,因为您的输出在失败前成功打印了两次字符串,然后再次成功。
  • 正如您已经提到的,自动刷新也不重要
  • Niko 的建议有一些优点,用于诊断目的。再加上马克的建议,这让我怀疑是否有一些无形的控制角色参与其中。如果您打印字符字节值作为诊断步骤怎么办?
  • 知道该值为“测试”(至少在您给我们的输出中)。如果将“Test”直接传递给失败的 printLn 语句会发生什么?
  • 在这种情况下,您希望获得尽可能多的信息。插入断点并分析字符。将字节发送到文件并在十六进制编辑器中打开它们。尽你所能尽可能准确和准确地追踪事物。
  • 想出奇怪的测试场景并尝试它们,即使它们可能没有帮助。在分析绝望想法的结果时,您永远不知道自己可能有什么好主意。
于 2009-08-27T03:11:44.883 回答
1

我猜想在 T 前面有一个虚假字节。根据 JavaDocs,InputStreamReader 将读取一个或多个字节,并将它们解码为字符。

你可能有一个转义序列或虚假字节,伪装成一个多字节字符。

快速检查 - 查看“当前”是否曾经 > 128 或 < 33。

如果您使用 CharArrayReader 来获取单个字节,而不进行任何字符集转换怎么办?

理论是,在第一次尝试使用 println 输出字符串时,它会发送某种转义字符,吃掉字符串的其余部分。在以后的打印过程中,Java 或网络管道正在处理或删除它,因为它之前获得了该转义序列,可能以某种方式改变了处理。

sb.toString() 作为一个不相关的 nit,返回一个新的字符串,所以没有必要调用 "new String(sb.toString())"

于 2009-08-28T15:54:38.510 回答
1

同样的问题,这些天我一遍又一遍地浏览这篇文章,希望我能找到一些解决方案。然后我决定我应该放弃 psexec 并找到一些替代方案。所以这就是事情:PAExec。非常适合获取命令输出。

于 2014-09-17T04:30:43.947 回答
0

你是如何执行 PsExec 的?我怀疑这是 PsExec 中的一些代码,它实际上正在执行回声抑制,可能是为了保护密码。检验此假设的一种方法是更改​​此代码:

    System.out.println("Outputting it cleanly to standard out: ");
    System.out.println(input);
    System.out.println("Outputting it cleanly to standard out again: ");
    System.out.println(input);

对此:

    System.out.println("Outputting it cleanly to standard out: ");
    System.out.print(' ');
    System.out.println(input);
    System.out.println("Outputting it cleanly to standard out again: ");
    System.out.println(input);

...从而导致输出为(如果我是对的):

Outputting it cleanly to standard out:
 Test
Outputting it cleanly to standard out again:
Test
Finished example outputs of input: Test

特别值得注意的是,明显被抑制的行是第一行,其中仅包含Test- 这正是您刚刚发送到远程系统的文本。这听起来像是 PsExec 试图抑制一个远程系统,该系统除了产生自己的输出之外还回显其输入。

可能是远程机器上用户的密码Test吗?你在使用 PsExec 的-p参数吗?你在指定-i吗?

于 2009-08-28T10:50:51.087 回答
0

我正在处理同样的问题,我想知道它是否与 cmd 窗口和 windows 中的管道在您没有真正的窗口会话时如何工作有关。当产生任何新进程时,会发生抑制输出。您会认为,如果您生成一个进程,stdout/stderr/stdin 将从生成它的进程继承;毕竟,如果您从普通 cmd 窗口生成进程并将新进程的输出通过管道传送回您自己的控制台,就会发生这种情况。但是,如果在管道继承的某个地方出现问题,例如说因为没有物理窗口而不传递 WINDOW.GUI 对象,则 Windows 不会让 stdin/stdout/stdin 被继承。任何人都可以为此进行一些调查或打开 Windows 支持票吗?

于 2013-04-10T02:59:10.210 回答
0

似乎没有简单的解决方案。我在最近的一个项目中的解决方法是使用 paexec.exe 产品。它在 JAVA(java-8) 中轻松捕获输出/错误,但在远程命令执行完成后挂断。在托管机器上的服务器中运行它时,我必须拒绝一个新的子 JVM 进程来运行 paexec.exe 并在完成后通过其 PID 强制终止它以释放所有资源。如果有人有更好的解决方案,请发布。

于 2017-07-11T16:38:06.543 回答