通过 PsExec 时程序输出丢失
(这是我的同事在其他地方发布的问题,但我想我应该发布放在这里是为了看看我是否能吸引不同的观众。)
大家好, 我正在测试编写一个小型 java 应用程序的可能性,该应用程序将使用 Psexec 启动远程作业。 在测试将 java 程序的 stdin 和 stdout 绑定到 psexec 的过程中,我遇到了一个奇怪的错误。
我的测试程序是一个基本的 echo 程序。 它启动一个线程从 stdin 读取数据,然后将读取的输出直接传送回 stdout。 当在本地计算机上运行而不是通过 psexec 运行时,它运行得非常好。 正是如此。
但是,当我第一次从 PsExec 调用它时,输入直接通过管道传输到 stdout 中,它会丢失。 这个 bug 真正奇怪的地方在于,只有当输入第一次直接通过管道传输到 stdout 时,它才会丢失。 如果输入字符串附加到另一个字符串,它就可以正常工作。 字符串文字或字符串变量。 但是,如果输入字符串直接发送到标准输出,则它不会通过。 第二次将其发送到标准输出时,它会顺利进行 - 此后每次都会如此。
我完全不知道这里发生了什么。 我尝试测试我能想到的每一个可能的错误。 我没主意了。 我是否错过了一个或者这只是 psexec 中的东西?
这是有问题的代码,它位于三个类中(其中一个类实现了一个接口,该接口是单个函数接口)。
Main 类:
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 类,它是从 stdin 读取的线程:
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 类,是将其通过管道返回到 stdout 的类:
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 - Execute processes remotely Copyright (C) 2001-2009 Mark Russinovich Sysinternals - www.sysinternals.com Starting up. Successfully started up. Awaiting input. Test Recieved character: T Recieved character: e Recieved character: s Recieved character: t Input string has made it to fireInputRecieved: Test START INPUT RECIEVED The input that has been recieved is: Test It is a String, that has been copied from a StringBuilder's toString(). Outputting it cleanly to standard out: Outputting it cleanly to standard out again: Test Finished example outputs of input: Test END INPUT RECIEVED
(This is a question my coworker posted elsewhere, but I thought I'd post it here to see if I could hit a different audience.)
Hello all,
I'm testing the possibility of writing a small java application the will use Psexec to kick off remote jobs. In the course of testing binding the stdin and stdout of a java program to psexec I came across an odd bug.
My test program is a basic echo program. It starts a thread to read from stdin and then pipes the read output directly back to stdout. When run on the local machine, not from psexec, it works beautifully. Exactly as it should.
However, when I call it from PsExec the first time the input is piped directly into stdout it is lost. What makes the bug really bizzare is that it is only the first time the input is piped directly into stdout that it is lost. If the input String is appended to another string it works fine. Either a String literal or a String variable. However, if the input String is sent directly to stdout it doesn't go through. The second time it is sent to stdout it goes through fine - and everytime there after.
I'm at a complete loss as to what's going on here. I've tried to test for every possible bug I can think of. I'm out of ideas. Did I miss one or is this just something inside psexec?
Here is the code in question, it's in three classes (one of which implements an interface which is a single function interace).
The Main class:
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.");
}
}
The CReader class which is the thread that reads from stdin:
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;
}
}
}
}
The CEcho class, which is the class that pipes it back to stdout:
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");
}
}
And finally, here is the program output:
>psexec \\remotecomputer "C:\Program Files\Java\jre1.6.0_05\bin\java.exe" -jar "C:\Documents and Settings\testProram.jar" PsExec v1.96 - Execute processes remotely Copyright (C) 2001-2009 Mark Russinovich Sysinternals - www.sysinternals.com Starting up. Successfully started up. Awaiting input. Test Recieved character: T Recieved character: e Recieved character: s Recieved character: t Input string has made it to fireInputRecieved: Test START INPUT RECIEVED The input that has been recieved is: Test It is a String, that has been copied from a StringBuilder's toString(). Outputting it cleanly to standard out: Outputting it cleanly to standard out again: Test Finished example outputs of input: Test END INPUT RECIEVED
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(13)
您是否尝试过将输出重定向到文件( java... >c:\output.txt )? 这样你就可以仔细检查所有内容是否都进入标准输出,并且可能只是被 psexec 吃掉
have you tried redirecting the output into a file ( java... >c:\output.txt )? this way you could doublecheck if everything is going into stdout and maybe just getting eaten by psexec
PsExec 正在吃掉输出。 下一个有趣的事情可能是它在哪里吃掉输出。 您可以通过获取 Wireshark 的副本并检查有问题的输出是否遍历网络来检查这一点。 如果不是,那么它就会在远端被吃掉。 如果是的话,那就是当地吃的。
并不是说我真的确定从哪里开始,但收集更多信息似乎是一条不错的道路......
PsExec is eating the output. Next interesting thing might be where it's eating the output. You could check this by getting a copy of Wireshark and checking whether the output in question is traversing the network or not. If it's not, then it's being eaten on the remote side. If it is, it's being eaten locally.
Not that I'm really sure where to go from there, but collecting more information certainly seems like a good path to be following...
我遇到了同样的问题并尝试了多种重定向组合。
这就是有效的:
processBuilder.redirectInput 的 Redirect.INHERIT 为我提供了丢失的远程命令输出。
I was having the same issue and tried multiple combinations of redirects.
This is what worked:
The Redirect.INHERIT for processBuilder.redirectInput got me the missing remote command output.
System.out
是否未配置自动刷新? 第一次打印后,尝试 System.out.flush() 并查看是否出现第一行而没有打印更多行。(哦,是的,说真的,这是“已收到”,而不是“已收到”。)
Is
System.out
not configured for autoflush? After the first print trySystem.out.flush()
and see if the first line appears without more lines being printed.(oh yeah, seriously, it is "RECEIVED", not "RECIEVED".)
好吧,我整个周末一直在考虑这个问题,自从你从一台机器跳到另一台机器后,我想知道是否可能存在字符集问题? 也许它是第一次吃字符串并处理不同的代码页或字符集问题? Java 通常是 16 位字符,而 Windows 要么是带代码页的 8 位字符,要么是现在的 utf-8。
本地和远程计算机有可能具有不同的默认字符集吗? 如果您通过网络发送本地化数据,它可能会出现错误。
OK, I've been thinking about this over the weekend and I since you are jumping from machine to machine I wonder if maybe there is a CharSet issue? Maybe it is eating the string the first time and dealing with a different code page or character set issue? Java is 16bit characters normally and windows is either 8bit with code pages or utf-8 these days.
Any chance the local and remote machines have different default character sets? If you are sending localized data over the net it might misbehave.
我在运行 psexec 时看到的是,它会生成一个子窗口来完成工作,但不会将该程序的输出返回到它的控制台窗口。 我建议使用 WMI 或某种形式的 Windows 进程 API 框架来获得 psexec 所缺乏的控制级别。 当然,java 有一个相当于 .Net 的 System.Diagnotics.Process 类。
What I see when running psexec is that it spawns a child window to do the work but doesnt return that program's output to it's console window. I would suggest using WMI or some form of windows process API framework to gain a level of control you appear to lack with psexec. Surely java has an equivalent to .Net's System.Diagnotics.Process class.
也许您可以尝试将输入的副本传递给侦听器:
我对侦听器也有类似的问题,除非我确实传递了它的显式副本,否则传递的变量最终会为空。
Maybe you could try passing a copy of input to your listeners:
I had similar problems with listeners where a passed variable would end up empty unless I did pass an explicit copy of it.
我不一定有答案,但一些评论可能会有所帮助。
I don't necessarily have an answer, but some comments may prove helpful.
我猜测 T 前面有一个假字节。根据 JavaDocs,InputStreamReader 将读取一个或多个字节,并将它们解码为字符。
那里可能有转义序列或虚假字节,伪装成多字节字符。
快速检查 - 看看“当前”是否曾经 > 128或< 33.
如果您使用 CharArrayReader 来获取单个字节而不进行任何字符集转换会怎么样?
理论是,在第一次尝试使用 println 输出字符串时,它会发送某种转义字符,吃掉字符串的其余部分。 在以后的打印过程中,Java 或网络管道正在处理或删除它,因为它之前获得了转义序列,可能以某种方式改变了处理。
作为一个不相关的 nit,sb.toString() 返回一个新的 String,因此没有必要调用“new String(sb.toString())”
I'd guess that there is a bogus byte in there prefacing the T. According to JavaDocs, an InputStreamReader will read one or more bytes, and decode them into characters.
You could have an escape sequence or spurious byte in there, masquerading as a multibyte character.
Quick check - see if "current" is ever > 128 or < 33.
What if you used a CharArrayReader to get individual bytes, without any charset translation?
The theory is that during the first attempt to output the String using println, it's sending an escape character of some sort, eating the rest of the string. During later prints, either Java or the network pipe are handling or removing it, since it previously got that escape sequence, perhaps changing the handling in some way.
As an unrelated nit, sb.toString() returns a new String, so it's unnecessary to call "new String(sb.toString())"
同样的问题,这些天我一遍又一遍地浏览这篇文章,希望能找到一些解决方案。 然后我决定放弃 psexec 并寻找其他选择。 这就是事情:PAExec。 非常适合获取命令输出。
Same issue here, I'm going through this post again and again these days, hoping I can find some solution. Then I decide I should give up psexec and find some alternative. So this is the thing: PAExec. Works perfect for getting command output.
你如何执行 PsExec? 我怀疑这是 PsExec 中的一些代码,实际上是在进行回声抑制,可能是为了保护密码。 测试这个假设的一种方法是将这段代码更改为:
...
从而导致输出为(如果我是对的):
特别是,值得注意的是,明显被抑制的行是由以下内容组成的第一行仅包含
Test
- 这正是您刚刚发送到远程系统的文本。 这听起来像是 PsExec 试图抑制远程系统,该系统除了产生自己的输出之外还回显其输入。远程计算机上的用户密码可能是
Test
吗? 您是否使用 PsExec 的-p
参数? 您是否指定了-i
?How are you executing PsExec? My suspicion is that this is some code within PsExec which is actually doing echo suppression, possibly for the purposes of protecting a password. One way to test this hypothesis would be to change this code:
to this:
...thereby causing the output to be (if I'm right):
In particular, it's noticeable that the apparently-suppressed line is the first line which consists solely of
Test
- which is exactly the text you've just sent to the remote system. This sounds like PsExec attempting to suppress a remote system which is echoing its input in addition to producing its own output.Is the password of the user on the remote machine perhaps
Test
? Are you using PsExec's-p
parameter? Are you specifying-i
?我正在处理同样的问题,我想知道它是否与 Windows 中的 cmd 窗口和管道在没有真正的窗口会话时工作的方式有关。 当任何新进程产生时,就会发生抑制的输出。 您可能会认为,如果生成一个进程,则 stdout/stderr/stdin 将从生成它的进程继承; 毕竟,如果您从普通的 cmd 窗口生成进程,并且新进程的输出通过管道传输回您自己的控制台,就会发生这种情况。 但是,如果管道继承中的某个地方出现问题,例如由于没有物理窗口而未传递 WINDOW.GUI 对象,则 Windows 不会让 stdin/stdout/stdin 被继承。 有人可以对此进行一些调查或开具 Windows 支持票吗?
I am dealing with this same issue and I am wondering if it has to do with how the cmd window and pipes in windows work while you don't have a true windowed session. The suppressed output happens when any new process is spawned. You would think that if you spawn a process that the stdout/stderr/stdin would be inherited from the process that spawned it; after all that is what happens if you spawn the process from a normal cmd window and the output from the new process is piped back to your own console. However if somewhere in the inheritance of the pipes something were to go wrong, like say not passing a WINDOW.GUI object because there is no physical window, windows doesn't let the stdin/stdout/stdin to be inherited. Can any one do some investigation or open a windows support ticket for this?
似乎没有简单的解决方案。 我在最近的项目中的解决方法是使用 paexec.exe 产品。 它可以在 JAVA(java-8) 中轻松捕获输出/错误,但在远程命令执行完成后挂起。 当在托管计算机上的服务器内运行此程序时,我必须拒绝一个新的子 JVM 进程来运行 paexec.exe,并在完成后通过其 PID 强制终止它,以释放所有资源。
如果有人有更好的解决方案,请发布。
Seems no easy solution. My work-around in a recent project is using paexec.exe product. It captures output/error easily in JAVA(java-8), but hangs up upon completion of the remote command execution. When running this inside a server on the hosted machine, I have to spurn a new child JVM process to run paexec.exe and force kill it via its PID upon completion in order to release all the resources.
If anyone has better solution, please post it.