使用 Windows 脚本宿主从 WshShell.Exec 捕获输出
我编写了以下两个函数,并从 Windows Script Host 内运行的 JavaScript 调用第二个函数(“callAndWait”)。我的总体意图是从另一个命令行程序调用一个命令行程序。也就是说,我使用 cscript 运行初始脚本,然后尝试从该脚本运行其他内容(Ant)。
function readAllFromAny(oExec)
{
if (!oExec.StdOut.AtEndOfStream)
return oExec.StdOut.ReadLine();
if (!oExec.StdErr.AtEndOfStream)
return "STDERR: " + oExec.StdErr.ReadLine();
return -1;
}
// Execute a command line function....
function callAndWait(execStr) {
var oExec = WshShell.Exec(execStr);
while (oExec.Status == 0)
{
WScript.Sleep(100);
var output;
while ( (output = readAllFromAny(oExec)) != -1) {
WScript.StdOut.WriteLine(output);
}
}
}
不幸的是,当我运行我的程序时,我没有立即得到有关被调用程序正在做什么的反馈。相反,输出似乎断断续续,有时要等到原始程序完成,有时又似乎陷入了僵局。我真正想做的是让生成的进程实际上与调用进程共享相同的 StdOut,但我看不到有办法做到这一点。仅设置 oExec.StdOut = WScript.StdOut 不起作用。
是否有另一种方法来生成共享 StdOut 和 StdOut 的进程?启动过程的StdErr?我尝试使用“WshShell.Run(),但这给了我一个“权限被拒绝”错误。这是有问题的,因为我不想告诉我的客户更改他们的 Windows 环境的配置方式只是为了运行我的程序。
我能做些什么?
I wrote the following two functions, and call the second ("callAndWait") from JavaScript running inside Windows Script Host. My overall intent is to call one command line program from another. That is, I'm running the initial scripting using cscript, and then trying to run something else (Ant) from that script.
function readAllFromAny(oExec)
{
if (!oExec.StdOut.AtEndOfStream)
return oExec.StdOut.ReadLine();
if (!oExec.StdErr.AtEndOfStream)
return "STDERR: " + oExec.StdErr.ReadLine();
return -1;
}
// Execute a command line function....
function callAndWait(execStr) {
var oExec = WshShell.Exec(execStr);
while (oExec.Status == 0)
{
WScript.Sleep(100);
var output;
while ( (output = readAllFromAny(oExec)) != -1) {
WScript.StdOut.WriteLine(output);
}
}
}
Unfortunately, when I run my program, I don't get immediate feedback about what the called program is doing. Instead, the output seems to come in fits and starts, sometimes waiting until the original program has finished, and sometimes it appears to have deadlocked. What I really want to do is have the spawned process actually share the same StdOut as the calling process, but I don't see a way to do that. Just setting oExec.StdOut = WScript.StdOut doesn't work.
Is there an alternate way to spawn processes that will share the StdOut & StdErr of the launching process? I tried using "WshShell.Run(), but that gives me a "permission denied" error. That's problematic, because I don't want to have to tell my clients to change how their Windows environment is configured just to run my program.
What can I do?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
您无法以这种方式从脚本引擎中的 StdErr 和 StdOut 读取,因为正如 Code Master Bob 所说,没有非阻塞 IO。如果当您尝试从 StdOut 读取数据时,被调用的进程填满了 StdErr 上的缓冲区(大约 4KB),反之亦然,那么您将死锁/挂起。在等待 StdOut 时你会挨饿,并且它会阻塞等待你从 StdErr 读取。
实际的解决方案是将 StdErr 重定向到 StdOut,如下所示:
换句话说,传递给 CreateProcess 的内容是这样的:
这会调用解释命令行的 CMD.EXE。
/S /C
调用特殊的解析规则,以便剥离第一个和最后一个引号,其余部分按原样使用并执行通过 CMD.EXE。因此 CMD.EXE 执行以下命令:咒语
2>&1
将prog.exe
的 StdErr 重定向到 StdOut。 CMD.EXE 将传播退出代码。现在,您可以通过读取 StdOut 并忽略 StdErr 来成功。
缺点是 StdErr 和 StdOut 输出混合在一起。只要它们是可识别的,您就可以使用它。
You cannot read from StdErr and StdOut in the script engine in this way, as there is no non-blocking IO as Code Master Bob says. If the called process fills up the buffer (about 4KB) on StdErr while you are attempting to read from StdOut, or vice-versa, then you will deadlock/hang. You will starve while waiting for StdOut and it will block waiting for you to read from StdErr.
The practical solution is to redirect StdErr to StdOut like this:
In other words, what gets passed to CreateProcess is this:
This invokes CMD.EXE, which interprets the command line.
/S /C
invokes a special parsing rule so that the first and last quote are stripped off, and the remainder used as-is and executed by CMD.EXE. So CMD.EXE executes this:The incantation
2>&1
redirectsprog.exe
's StdErr to StdOut. CMD.EXE will propagate the exit code.You can now succeed by reading from StdOut and ignoring StdErr.
The downside is that the StdErr and StdOut output get mixed together. As long as they are recognisable you can probably work with this.
在这种情况下可能有用的另一种技术是重定向命令的标准错误流以伴随标准输出。
为此,请在 execStr 字符串的前面添加“%comspec% /c”,将“2>&1”添加到末尾。
也就是说,将运行的命令从: 更改
为:
“2>&1”是一个重定向指令,它会导致 StdErr 输出(文件描述符 2)写入 StdOut 流(文件描述符 1)。
您需要包含“%comspec% /c”部分,因为它是理解命令行重定向的命令解释器。请参阅 http://technet.microsoft.com/en-us/library/ee156605。 .aspx
使用“%comspec%”而不是“cmd”可以为更广泛的 Windows 版本提供可移植性。
如果您的命令包含带引号的字符串参数,则正确使用它们可能会很困难:
cmd 如何处理“/c”后的引号的规范似乎不完整。
这样,您的脚本只需要读取 StdOut 流,并且将接收标准输出和标准错误。
我将其与“net stop wuauserv”一起使用,成功时写入 StdOut(如果服务正在运行)
和失败时的 StdErr(如果服务已停止)。
Another technique which might help in this situation is to redirect the standard error stream of the command to accompany the standard output.
Do this by adding "%comspec% /c" to the front and "2>&1" to the end of the execStr string.
That is, change the command you run from:
to:
The "2>&1" is a redirect instruction which causes the StdErr output (file descriptor 2) to be written to the StdOut stream (file descriptor 1).
You need to include the "%comspec% /c" part because it is the command interpreter which understands about the command line redirect. See http://technet.microsoft.com/en-us/library/ee156605.aspx
Using "%comspec%" instead of "cmd" gives portability to a wider range of Windows versions.
If your command contains quoted string arguments, it may be tricky to get them right:
the specification for how cmd handles quotes after "/c" seems to be incomplete.
With this, your script needs only to read the StdOut stream, and will receive both standard output and standard error.
I used this with "net stop wuauserv", which writes to StdOut on success (if the service is running)
and StdErr on failure (if the service is already stopped).
首先,您的循环被破坏,因为它总是首先尝试从
oExec.StdOut
读取。如果没有实际输出,那么它将挂起直到有实际输出。在StdOut.atEndOfStream
变为 true 之前(可能是在子进程终止时),您不会看到任何StdErr
输出。不幸的是,脚本引擎中没有非阻塞 I/O 的概念。这意味着如果缓冲区中没有数据,则调用 read 并立即返回。因此,可能没有办法让这个循环按您想要的方式工作。其次,WShell.Run
不提供任何属性或方法来访问子进程的标准 I/O。它在一个单独的窗口中创建子窗口,除了返回代码之外,与父窗口完全隔离。但是,如果您想要的只是能够看到孩子的输出,那么这可能是可以接受的。您还可以与子项进行交互(输入),但只能通过新窗口进行交互(请参阅SendKeys
)。至于使用 ReadAll() ,情况会更糟,因为它会在返回之前收集流中的所有输入,因此在流关闭之前您根本看不到任何内容。我不知道为什么示例将
ReadAll
放置在构建字符串的循环中,单个if (!WScript.StdIn.AtEndOfStream)
应足以避免异常。另一种选择可能是使用 WMI 中的进程创建方法。标准 I/O 的处理方式尚不清楚,并且似乎没有任何方法可以将特定流分配为
StdIn
/Out
/Err
。唯一的希望是孩子能从父母那里继承这些,但这就是你想要的,不是吗? (此评论基于一个想法和一些研究,但没有实际测试。)基本上,脚本系统并不是为复杂的进程间通信/同步而设计的。
注意:确认上述内容的测试是在 Windows XP Sp2 上使用脚本版本 5.6 进行的。参考当前 (5.8) 手册表明没有变化。
First, your loop is broken in that it always tries to read from
oExec.StdOut
first. If there is no actual output then it will hang until there is. You wont see anyStdErr
output untilStdOut.atEndOfStream
becomes true (probably when the child terminates). Unfortunately, there is no concept of non-blocking I/O in the script engine. That means callingread
and having it return immediately if there is no data in the buffer. Thus there is probably no way to get this loop to work as you want. Second,WShell.Run
does not provide any properties or methods to access the standard I/O of the child process. It creates the child in a separate window, totally isolated from the parent except for the return code. However, if all you want is to be able to SEE the output from the child then this might be acceptable. You will also be able to interact with the child (input) but only through the new window (seeSendKeys
).As for using
ReadAll()
, this would be even worse since it collects all the input from the stream before returning so you wouldn't see anything at all until the stream was closed. I have no idea why the example places theReadAll
in a loop which builds a string, a singleif (!WScript.StdIn.AtEndOfStream)
should be sufficient to avoid exceptions.Another alternative might be to use the process creation methods in WMI. How standard I/O is handled is not clear and there doesn't appear to be any way to allocate specific streams as
StdIn
/Out
/Err
. The only hope would be that the child would inherit these from the parent but that's what you want, isn't it? (This comment based upon an idea and a little bit of research but no actual testing.)Basically, the scripting system is not designed for complicated interprocess communication/synchronisation.
Note: Tests confirming the above were performed on Windows XP Sp2 using Script version 5.6. Reference to current (5.8) manuals suggests no change.
是的,Exec 函数在终端输出方面似乎被破坏了。
我一直在使用类似的函数
function ConsumeStd(e) {WScript.StdOut.Write(e.StdOut.ReadAll());WScript.StdErr.Write(e.StdErr.ReadAll());}
我在类似于你的循环中调用它。不确定检查 EOF 和逐行读取是好是坏。Yes, the Exec function seems to be broken when it comes to terminal output.
I have been using a similar function
function ConsumeStd(e) {WScript.StdOut.Write(e.StdOut.ReadAll());WScript.StdErr.Write(e.StdErr.ReadAll());}
that I call in a loop similar to yours. Not sure if checking for EOF and reading line by line is better or worse.您可能遇到了此Microsoft 支持网站中描述的死锁问题。
一个建议是始终从
stdout
和stderr
读取。您可以将 readAllFromAny 更改为:
You might have hit the deadlock issue described on this Microsoft Support site.
One suggestion is to always read both from
stdout
andstderr
.You could change
readAllFromAny
to: