【问题标题】:How can I capture output from the Windows cmd shell?如何从 Windows cmd shell 捕获输出?
【发布时间】:2010-11-03 09:42:57
【问题描述】:

有没有什么办法,比如 Perl 或 PHP,我可以从另一个输出到 Windows cmd shell 的进程中获取输出?我有一个输出某些信息的游戏服务器,例如说“玩家在 43 秒内完成曲目”,我想抓住那条线并使用 Perl 或 PHP 向网络服务器发送请求以更新网页上的排名。有没有办法在 Perl 或 PHP 中获取该输出管道?或者我可以使用 C++ Windows API 来实现这一点吗?

让我在这里澄清一下:我想执行一个单独的 Perl 或 PHP 脚本,该脚本从 Windows cmd shell 获取输出,而显示到 Windows cmd shell 的输出来自不同的进程。

【问题讨论】:

    标签: php c++ windows perl


    【解决方案1】:

    您可以使用IPC::Open3 来读取其他进程的标准输出。请注意,进程间通信假定进程之间存在父/子关系。如果不是这种情况......我不知道附加到预先存在的进程的输出的机制。在这种情况下,您可能需要更改生产者以将数据写入您的应用程序可以读取的日志文件(或数据库)。

    【讨论】:

      【解决方案2】:

      如果你只关心STDOUT,你可以使用IPC::Open2的open2:

      #!/usr/bin/perl
      
      use strict;
      use warnings;
      
      use IPC::Open2;
      
      #if there are arguments pretend to be the server
      #for this example
      if (@ARGV) {
          local $| = 1;
          for my $i (1 .. 100) {
              print "pid $$ iter $i\n";
              sleep 1;
          }
          exit;
      }        
      
      #run perl with the current script as its argument,
      #pass in an arg so that we trigger the behaviour 
      #above
      open2 my $out, my $in, $^X, $0, 1 
          or die "could not run '$^X $0 1': $!\n";
      
      while (<$out>) {
          s/[\r\n]//g;
          print "pid $$ saw [$_]\n";
      }
      

      【讨论】:

      • 我回家后试试看
      • @Alexandr Ciornii 哇,糟透了,至少有 IPC::Open2 和 IPC::Open3。
      【解决方案3】:

      您需要在 Perl 中启动您的服务器:

      my $server_out = `server.exe`; # Note the backticks.
      

      现在 $server_out 包含 server.exe 的输出。但这里的诀窍是你必须等到 server.exe 退出才能输出。

      试试IPC::Run(不是核心模块)

      use English;
      use IPC::Run;
      my ($stdout, $stderr);
      
      IPC::Run::run([$cmd, $arg1, $arg2, $argN], \undef, \$stdout, $stderr);
      
      while(<$stdout>) {
        print "Cmd said $_\n";
      }
      

      注意:代码未经测试。

      找到信息here.

      【讨论】:

        【解决方案4】:

        在 Perl 中捕获输出很简单:

        $output = qx(command);
        

        $output = `command`;  # backticks
        

        参考:perldoc perlop

        【讨论】:

        • 这行不通,因为我只有在命令完成后才能访问 $output...而且服务器进程没有“完成”
        【解决方案5】:

        此代码将控制台应用程序的 STDOUT 重定向到字符串列表,例如,您可以在备忘录中使用它。是 Delphi 代码,但在 C++ 中基本思想是完全一样的。

        我使用它来运行隐藏的控制台应用程序,同时将输出重定向到我自己的应用程序,以显示在窗格中。一旦数据进入,它就会在 AStrings 中添加一个新行,因此您可以在其他应用程序完成之前访问它的输出。

        procedure RunConsoleApp(const CommandLine: string; AStrings: TStrings);
        type
          TCharBuffer = array[0..MaxInt div SizeOf(Char) - 1] of Char;
        const
          MaxBufSize = 1024;
        var
          I: Longword;
          SI: TStartupInfo;
          PI: TProcessInformation;
          SA: PSecurityAttributes;
          SD: PSECURITY_DESCRIPTOR;
          NewStdIn: THandle;
          NewStdOut: THandle;
          ReadStdOut: THandle;
          WriteStdIn: THandle;
          Buffer: ^TCharBuffer;
          BufferSize: Cardinal;
          Last: WideString;
          Str: WideString;
          ExitCode_: DWORD;
          Bread: DWORD;
          Avail: DWORD;
        begin
          GetMem(SA, SizeOf(TSecurityAttributes));
        
          case Win32Platform of
            VER_PLATFORM_WIN32_NT:
              begin
                GetMem(SD, SizeOf(SECURITY_DESCRIPTOR));
                SysUtils.Win32Check(InitializeSecurityDescriptor(SD, SECURITY_DESCRIPTOR_REVISION));
                SysUtils.Win32Check(SetSecurityDescriptorDacl(SD, True, nil, False));
                SA.lpSecurityDescriptor := SD;
              end; {end VER_PLATFORM_WIN32_NT}
          else
            SA.lpSecurityDescriptor := nil;
          end; {end case}
        
          SA.nLength := SizeOf(SECURITY_ATTRIBUTES);
          SA.bInheritHandle := True;
        
          SysUtils.Win32Check(CreatePipe(NewStdIn, WriteStdIn, SA, 0));
        
          if not CreatePipe(ReadStdOut, NewStdOut, SA, 0) then
          begin
            CloseHandle(NewStdIn);
            CloseHandle(WriteStdIn);
            SysUtils.RaiseLastWin32Error;
          end; {end if}
        
          GetStartupInfo(SI);
          SI.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
          SI.wShowWindow := {SW_SHOWNORMAL} SW_HIDE;
          SI.hStdOutput := NewStdOut;
          SI.hStdError := NewStdOut;
          SI.hStdInput := NewStdIn;
        
          if not CreateProcess(nil, PChar(CommandLine), nil, nil, True, CREATE_NEW_CONSOLE, nil, nil, SI, PI) then
          begin
            CloseHandle(NewStdIn);
            CloseHandle(NewStdOut);
            CloseHandle(ReadStdOut);
            CloseHandle(WriteStdIn);
            SysUtils.RaiseLastWin32Error;
          end; {end if}
        
          Last := '';
          BufferSize := MaxBufSize;
          Buffer := AllocMem(BufferSize);
        
          try
            repeat
              SysUtils.Win32Check(GetExitCodeProcess(PI.hProcess, ExitCode_));
              PeekNamedPipe(ReadStdOut, Buffer, BufferSize, @Bread, @Avail, nil);
        
              if (Bread <> 0) then
              begin
                if (BufferSize < Avail) then
                begin
                  BufferSize := Avail;
                  ReallocMem(Buffer, BufferSize);
                end; {end if}
                FillChar(Buffer^, BufferSize, #0);
                Windows.ReadFile(ReadStdOut, Buffer^, BufferSize, Bread, nil);
                Str := Last;
                I := 0;
        
                while (I < Bread) do
                begin
        
                  case Buffer^[I] of
                    #0: inc(I);
                    #7: begin
                          inc(I);
                          Windows.Beep(800, 50);
                          Str := Str + '^';
                        end;
                    #10:
                      begin
                        inc(I);
                        AStrings.Add(Str);
                        Str := '';
                      end; {end #10}
                    #13:
                      begin
                        inc(I);
                        if (I < Bread) and (Buffer^[I] = #10) then
                          inc(I);
                        AStrings.Add(Str);
                        Str := '';
                      end; {end #13}
                  else
                    begin
                      Str := Str + Buffer^[I];
                      inc(I);
                    end; {end else}
                  end; {end case}
                end; {end while}
                Last := Str;
              end; {end if}
              Sleep(1);
              Application.ProcessMessages;
        
            until (ExitCode_ <> STILL_ACTIVE);
        
            if Last <> '' then
              AStrings.Add(Last);
        
          finally
            FreeMem(Buffer);
          end; {end try/finally}
        
          CloseHandle(PI.hThread);
          CloseHandle(PI.hProcess);
          CloseHandle(NewStdIn);
          CloseHandle(NewStdOut);
          CloseHandle(ReadStdOut);
          CloseHandle(WriteStdIn);
        
        end; {end procedure}
        

        【讨论】:

        • 如果有人可以将其转换为 CPP,那就太棒了。
        【解决方案6】:

        这里是一个 PHP 特定的解决方案,该项目允许 PHP 获取并与一个真实的 cmd 终端动态交互。在这里获取:https://github.com/merlinthemagic/MTS

        下载后您只需使用以下代码:

        //if you prefer Powershell, replace 'cmd' with 'powershell'
        $shellObj    = \MTS\Factories::getDevices()->getLocalHost()->getShell('cmd');
        
        $strCmd1   = 'some_app.exe -param "test"';
        $return1   = $shellObj->exeCmd($strCmd1);
        

        return 将为您提供来自 cmd 的命令 return OR error,就像您坐在控制台前一样。

        此外,您可以针对 $shellObj 发出任何您喜欢的命令,该环境在 PHP 脚本的整个生命周期内都会得到维护。所以不用将命令捆绑在脚本文件中,只需使用 exeCmd() 方法一一发出,这样您还可以处理返回和任何异常。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-09-21
          • 2013-01-16
          • 1970-01-01
          • 2019-02-23
          • 2014-07-26
          相关资源
          最近更新 更多