如何从Windows cmd shell捕获输出?

时间:2021-01-14 14:05:56

Is there any way with, say Perl or PHP, that I can grab output from another process that outputs to the Windows cmd shell? I have a game server that outputs certain information, for example say 'player finished track in 43s' and I want to grab that line and use Perl or PHP to send a request to a webserver to update ranks on a web page. Is there a way to grab that output pipe in Perl or PHP? Or could I achieve this using C++ Windows API maybe?

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

Let me clarify here: I want to execute a seperate Perl or PHP script that grabs output from the Windows cmd shell, and the output that is being displayed to the Windows cmd shell is coming from a different process.

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

6 个解决方案

#1


You need to start your server within Perl:

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

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

Now $server_out contains the output of server.exe. But the trick here is that you have to wait until server.exe exits to get the out put.

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

Try IPC::Run (which is not a core module)

尝试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";
}

Note: Code not tested.

注意:代码未经过测试。

Found the info here.

在这里找到信息。

#2


You could use IPC::Open3 to read from the other process' standard output. Note that inter-process communication assumes a parent/child relationship between the processes. If that's not the case... I'm not aware of a mechanism for attaching to the output of a pre-existing process. In that case you may need to alter the producer to write data to a log file (or database) that your application can read from.

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

#3


If all you care about is STDOUT, you can just use open2 from IPC::Open2:

如果你关心的只是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";
}

#4


Capturing the output in Perl is as simple as:

捕获Perl中的输出非常简单:

$output = qx(command);

or

$output = `command`;  # backticks

Refer: perldoc perlop

参考:perldoc perlop

#5


This code redirects the STDOUT of a console application to a stringlist, which you can use on a memo for example. It's Delphi code, but in C++ the basic idea is exactly the same.

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

I use it to run console applications hidden, while redirecting the output to my own application, to show in a pane. It adds a new line to AStrings as soon as data comes in, so you'll have access to the output of the other application before it finishes.

我使用它来运行隐藏的控制台应用程序,同时将输出重定向到我自己的应用程序,以显示在窗格中。一旦数据进入,它就会向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}

#6


Here is a PHP specific solution, the project allows PHP to obtain and interact dynamically with a real cmd terminal. Get it here: https://github.com/merlinthemagic/MTS

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

After downloading you would simply use the following code:

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

//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);

The return will give you the command return OR error from cmd, just as if you sat at the console.

返回将为您提供cmd命令返回OR错误,就像您坐在控制台一样。

Furthermore, you can issue any command you like against the $shellObj, the environment is maintained throughout the life of the PHP script. So instead of bundling commands in a script file, just issue them one by one using the exeCmd() method, that way you can also handle the return and any exceptions.

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

#1


You need to start your server within Perl:

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

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

Now $server_out contains the output of server.exe. But the trick here is that you have to wait until server.exe exits to get the out put.

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

Try IPC::Run (which is not a core module)

尝试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";
}

Note: Code not tested.

注意:代码未经过测试。

Found the info here.

在这里找到信息。

#2


You could use IPC::Open3 to read from the other process' standard output. Note that inter-process communication assumes a parent/child relationship between the processes. If that's not the case... I'm not aware of a mechanism for attaching to the output of a pre-existing process. In that case you may need to alter the producer to write data to a log file (or database) that your application can read from.

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

#3


If all you care about is STDOUT, you can just use open2 from IPC::Open2:

如果你关心的只是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";
}

#4


Capturing the output in Perl is as simple as:

捕获Perl中的输出非常简单:

$output = qx(command);

or

$output = `command`;  # backticks

Refer: perldoc perlop

参考:perldoc perlop

#5


This code redirects the STDOUT of a console application to a stringlist, which you can use on a memo for example. It's Delphi code, but in C++ the basic idea is exactly the same.

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

I use it to run console applications hidden, while redirecting the output to my own application, to show in a pane. It adds a new line to AStrings as soon as data comes in, so you'll have access to the output of the other application before it finishes.

我使用它来运行隐藏的控制台应用程序,同时将输出重定向到我自己的应用程序,以显示在窗格中。一旦数据进入,它就会向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}

#6


Here is a PHP specific solution, the project allows PHP to obtain and interact dynamically with a real cmd terminal. Get it here: https://github.com/merlinthemagic/MTS

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

After downloading you would simply use the following code:

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

//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);

The return will give you the command return OR error from cmd, just as if you sat at the console.

返回将为您提供cmd命令返回OR错误,就像您坐在控制台一样。

Furthermore, you can issue any command you like against the $shellObj, the environment is maintained throughout the life of the PHP script. So instead of bundling commands in a script file, just issue them one by one using the exeCmd() method, that way you can also handle the return and any exceptions.

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