12

我编写了以下两个函数,并从运行在 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 和 StdErr 的进程?我尝试使用“WshShell.Run(),但这给了我一个“权限被拒绝”的错误。这是有问题的,因为我不想告诉我的客户更改他们的 Windows 环境的配置方式只是为了运行我的程序。

我能做些什么?

4

5 回答 5

11

您无法以这种方式从脚本引擎中的 StdErr 和 StdOut 读取,因为没有像 Code Master Bob 所说的非阻塞 IO。如果在您尝试从 StdOut 读取数据时被调用的进程填满了 StdErr 上的缓冲区(大约 4KB),反之亦然,那么您将死锁/挂起。在等待 StdOut 时你会饿死,它会阻塞等待你从 StdErr 读取。

实际的解决方案是将 StdErr 重定向到 StdOut,如下所示:

sCommandLine = """c:\Path\To\prog.exe"" Argument1 argument2"
Dim oExec
Set oExec = WshShell.Exec("CMD /S /C "" " & sCommandLine & " 2>&1 """)

换句话说,传递给 CreateProcess 的是:

CMD /S /C " "c:\Path\To\prog.exe" Argument1 argument2 2>&1 "

这将调用 CMD.EXE,它解释命令行。/S /C调用一个特殊的解析规则,以便去掉第一个和最后一个引号,其余部分按原样使用并由 CMD.EXE 执行。所以 CMD.EXE 执行这个:

"c:\Path\To\prog.exe" Argument1 argument2 2>&1

咒语将 的 StdErr2>&1重定向prog.exe到 StdOut。CMD.EXE 将传播退出代码。

您现在可以通过读取 StdOut 并忽略 StdErr 来成功。

缺点是 StdErr 和 StdOut 输出混合在一起。只要它们是可识别的,您就可以使用它。

于 2012-01-30T11:30:35.180 回答
4

在这种情况下可能有帮助的另一种技术是将命令的标准错误流重定向到标准输出。通过在前面添加“%comspec% /c”并将“2>&1”添加到 execStr 字符串的末尾来执行此操作。也就是说,更改您运行的命令:

zzz

至:

%comspec% /c zzz 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(如果服务已经停止)。

于 2012-01-17T20:13:03.980 回答
2

首先,你的循环被打破了,它总是试图从oExec.StdOut第一个开始读取。如果没有实际输出,那么它将一直挂起,直到有。在变为真之前(可能在孩子终止时),您不会看到任何StdErr输出。StdOut.atEndOfStream不幸的是,脚本引擎中没有非阻塞 I/O 的概念。这意味着如果缓冲区中没有数据,则调用read并立即返回。因此,可能没有办法让这个循环按你的意愿工作。第二,WShell.Run不提供任何属性或方法来访问子进程的标准 I/O。它在一个单独的窗口中创建子窗口,除了返回代码之外,它与父窗口完全隔离。但是,如果您想要的只是能够看到孩子的输出,那么这可能是可以接受的。您还可以与孩子互动(输入),但只能通过新窗口(请参阅 参考资料SendKeys)。

至于 using ReadAll(),这会更糟,因为它会在返回之前从流中收集所有输入,因此在流关闭之前你什么都看不到。我不知道为什么该示例将其放置ReadAll在构建字符串的循环中,单个if (!WScript.StdIn.AtEndOfStream)应该足以避免异常。

另一种选择可能是使用 WMI 中的进程创建方法。如何处理标准 I/O 尚不清楚,似乎没有任何方法可以将特定流分配为StdIn// OutErr唯一的希望是孩子会从父母那里继承这些,但这就是你想要的,不是吗?(此评论基于一个想法和一些研究,但没有实际测试。)

基本上,脚本系统不是为复杂的进程间通信/同步而设计的。

注意:确认上述内容的测试是在 Windows XP Sp2 上使用脚本版本 5.6 执行的。参考当前(5.8)手册表明没有变化。

于 2011-10-17T23:41:29.800 回答
1

是的,当涉及到终端输出时,Exec 函数似乎被破坏了。

我一直在使用一个类似的函数function ConsumeStd(e) {WScript.StdOut.Write(e.StdOut.ReadAll());WScript.StdErr.Write(e.StdErr.ReadAll());},我在类似于你的循环中调用它。不确定检查 EOF 和逐行阅读是好是坏。

于 2010-01-23T11:35:57.570 回答
1

您可能遇到了此Microsoft 支持站点上描述的死锁问题。

一个建议是始终从stdout和读取stderr。您可以更改readAllFromAny为:

function readAllFromAny(oExec)
{
  var output = "";

  if (!oExec.StdOut.AtEndOfStream)
    output = output + oExec.StdOut.ReadLine();

  if (!oExec.StdErr.AtEndOfStream)
    output = output + "STDERR: " + oExec.StdErr.ReadLine();

  return output ? output : -1;
}
于 2016-02-05T11:53:37.520 回答