ActivePerlの標準入出力の操作

解決


kuze  2009-02-23 07:24:19  No: 33430

doscommand.pas(http://maxxdelphisite.free.fr/)を使用しているのですが、ActivePerl 5.10の標準入出力が上手く操作できません。
他のツールは大体上手くいくのですが、ActivePerlは特殊なのでしょうか?
アドバイスよろしくお願いします。

test.pl
------------
print "Start";
while(<>){
  print "Loop";
}
print "End";
------------

unit1.pas
  Button1で、test.pl実行
  Button2で、perlの標準入力にEdit2の内容を送信
  Button3で、ファイル終端記号送信
  標準出力は、Memo1にされます。
  DosCommand1のInputToOutputは、True。ReturnCodeは、rcCRLFに設定。
---------------------------
procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Lines.Add('perl.exe test.pl');
  DosCommand1.CommandLine := 'perl.exe test.pl';
  DosCommand1.Execute;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  DosCommand1.SendLine(Edit2.Text, True);
end;

procedure TForm1.FormShow(Sender: TObject);
begin
  DosCommand1.OutputLines := Memo1.Lines;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  DosCommand1.SendLine(#$1A, True);
end;


なる  2009-02-24 08:22:34  No: 33431

上手く行かないのはいいとして、何がどう上手くいかないんですか?


kuze  2009-02-24 09:24:28  No: 33432

Perlとの標準入出力が上手くいかないのです。

test.plは、
1. Startを標準出力に出力
2. 改行つきの標準入力を受け取るたびに、Loopを標準出力に出力
3. ファイル終端記号を受け取ると、Endを出力
というプログラムですが、
標準入力、出力ともDelphi側とやり取りできない状態です。
ちなみに、
test.plを
print "Start";
のみにすると、標準出力のStartを受け取ることができます。
while以降の処理が入ると動作しなくなります。
当然ですが、コマンドプロンプトからは、test.plは正常に動作します。


TOBY  2009-03-12 00:33:46  No: 33433

Stdoutがフラッシュされてないって可能性ないですかね。

私はよくRubyを使いますが、標準出力をflushしないとcygwinのコンソールとかでも逐一表示されなかったりします。

flushの仕方はperlだと、

$| = 1;

を上の方に1つかいておいて、自動で出力時にflushさせるか、
参考:
日本語 perl texinfo - $vline
http://flex.ee.uec.ac.jp/texi/perl/perl_161.html#SEC226

書き出すたびに、明示的にflush関数を呼び出す、
flush('STDOUT');

でいけるんじゃないかな?
私はPerlは詳しくないので、
"Perl flush stdout"とかでgoogleさん検索してみてください、という感じです。


TOBY  2009-03-12 00:36:10  No: 33434

変ないい回しでした。

> Stdoutがフラッシュされてないって可能性ないですかね。

STDOUTがバッファリングされているのでflushしないといけない可能性はないですかね。

ということです。


kuze  2009-03-12 05:53:33  No: 33435

TOBYさん、ご意見ありがとうございます。
僕もPerl詳しくないので、いただいた意見を参考に検討してみます。
ただ、標準入力も動作がおかしいと思っています。
Button3を押すと、Loopから抜けてperlのプログラムは終了するはずですが、終了しないので、入力値がPerlに届いていないのでは?
と思っています。


TOBY  2009-03-14 19:01:18  No: 33436

コンソールでテストコードを書いてみました。

標準入力や出力の受渡は正常にできてるみたいです。
(フラッシュしたらプログラム側で受け取れます)

ただ、標準入力で#$1A を渡してもループを抜けられませんね。
コマンドラインでも、ファイルに1Aをバイナリで記述して渡すと終了しません。
テキストモードだと行ける、という話がありますが、
 binmode STDIN, ":crlf" 
とかしても上手くいかず。
うーん。

とりあえず、Delphiは関係ない気がしますw

-------
program console_test;
{$APPTYPE CONSOLE}
uses
  SysUtils, Windows, DosCommand in '..\DosCommand.pas';
type
  TDosCommandTest = class
    procedure OnNewLine(Sender: TObject; NewLine: string; OutputType: TOutputType);
    function Main: TDosCommandTest;
  end;
procedure TDosCommandTest.OnNewLine(Sender: TObject; NewLine: string; OutputType: TOutputType);
begin
  if OutputType = otEntireLine then Writeln('Cmd: ', NewLine);
end;

function TDosCommandTest.Main(): TDosCommandTest;
var
  i: Integer;
Cmd: TDosCommand;
begin
  Cmd := TDosCommand.Create(nil);
  try
    Cmd.CommandLine := 'perl test.pl';
    Cmd.InputToOutput := True;
    Cmd.OnNewLine := OnNewLine;
    Cmd.Execute;
    // wait
    while Cmd.IsRunning do begin
      // ESCAPEで強制終了
      if (GetAsyncKeyState(VK_ESCAPE) and $8000) <> 0 then break;
      // Zでテスト出力
      if (GetAsyncKeyState(Ord('Z')) and $8000) <> 0 then
        Cmd.SendLine('test', True);
      // Xで終了コード?を送る (うまくいかず)
      if (GetAsyncKeyState(Ord('X')) and $8000) <> 0 then
        Cmd.SendLine(#$1A, True);

      Sleep(100);
    end;
  finally
    FreeAndNil(Cmd);
  end;
  Writeln('DONE!');
  Result := Self;
end;

begin
  TDosCommandTest.Create.Main.Free;
end.
-----test.pl-----
$| = 1;
print "Start\n";
$i = 0;
while($a = <>){
  print "Loop: $a $i\n";
  $i++;
}
print "End\n";


kuze  2009-03-15 07:12:42  No: 33437

TOBYさん、久世です。
サンプルコードありがとうございました。
何点か質問があります。
・TDosCommandのIsRunningっていうプロパティーは新規に追加されましたか?
・test.plの $|=1 がフラッシュと関係あるのでしょうか?
・IsRunningは、Activeに変更して私の方で試しましたが、標準出力が上手くされません。

ちなみに、OSはVistaですが、あまり関係と思っています。


kuze  2009-03-15 07:14:14  No: 33438

すみません。
$l=1の説明は、前回教えてもらっていましたね。


kuze  2009-03-15 08:23:08  No: 33439

IsRunningっていうプロパティーありますね。
僕の使用していたものより、新しいバージョンには入ってました。
失礼いたしました。


TOBY  2009-03-18 19:49:09  No: 33440

どこまで上手くいきましたか?

先ほどの私のコードでは、標準出力は問題なく取れています。
標準入力も問題ないです。
問題は、EOFが検出できないことです。
私のPerl(というか標準入出力周りの)理解力不足のせいもあります。

この件について、数日前からちょっと、2chのPerlスレで聞いてみてます。
(こっちの方が詳しいし早いと思ったので)

Perlについての質問箱 39箱目
http://pc11.2ch.net/test/read.cgi/tech/1234181856/311-

続いて質問してみているのですが、
たぶんに、while(<>)で検出ではなく、プログラムから標準入力を出す場合は、文字列内にEOFがあったら、というように検出しないといけないような気がします。(ファイルで渡した時と同様に)


TOBY  2009-03-18 19:57:32  No: 33441

TDosCommand.IsRunningプロパティは、
TDosCommand.Execute を呼び出すと、そのままスルーしてプログラムが実行されていくので、
(=Perlが終了するまで待ってくれない)
コンソールプログラムだとプログラムが終了してしまうのを防止するために
使っています。

TFormなどGUIですぐに終了しないなら必要ないかと。(逆に終了を待つ必要があるなら必要)

標準出力のうけとりはOnNewLineイベントを使っていますが、コードを見たところ、
OutputLinesプロパティでも問題なさそうですが…。


kuze  2009-03-19 06:19:59  No: 33442

TOBYさん、いろいろ調べてもらってありがとうございます。

1. doscommand.zip
2. doscommand_tk.zip
の2種類のVCLがありますが、2だと標準入出力が上手くいきませんが、
1だと、TOBYさんと同じ状況になるのを確認しました。
1, 2でソースは大分差がありますが、どの部分がクリティカルに
利いているか悩んでいます。
また、ファイル終端が上手く検出できないのがまだ不思議でなりません。
コマンドプロンプトではCtrl+Zで上手くいっていますので・・・


TOBY  2009-03-19 21:41:46  No: 33443

ああ、すいません。使ったライブラリ書いておくべきでした。
使ったのはdoscommand.zipの方です。
doscommand_tk.zipは挙動が違うということなのかな…?

> また、ファイル終端が上手く検出できないのがまだ不思議でなりません。
これについては2chの方でお聞きしていたのですが、大体結論が出た感じです。

Perlについての質問箱 39箱目
http://pc11.2ch.net/test/read.cgi/tech/1234181856/352-353

> 352  デフォルトの名無しさん  [sage]  2009/03/18(水) 20:17:40
> >>344
> だから 0x1a が EOF と思ってる時点で間違ってる
> まずここを理解しろよ

> CTRL-Z 押下は EOF を送るための特殊な操作であって、
> それが歴史的な理由で CTRL-Z (0x1a)になってるだけであって
> 0x1a を送れば EOF になるわけじゃない。

> 353  デフォルトの名無しさん  [sage]  2009/03/19(木) 01:24:01
> >>344
> if (ナントカ eq "\x1a") { exit }

> こういう発想は思いつかんの?

ということらしいです。

コマンドプロンプトでのCtrl+Zと、プログラムで入力した0x1Aは別物、ということみたいです。
なので、0x1Aがあったら中断するように、自分でPerl側で処理しないといけない、ということになるのでしょう。


kuze  2009-03-20 07:25:58  No: 33444

doscommand_tk.zipは挙動が違います。

また、このperlプログラムは、
標準入力を受け付けるので、ファイルからの入力も以下のようにいけます。

perl test.pl < input.txt

この場合は、間違いなく最後に入力されるのは
EOFだと思うのですが・・・

以上よろしくお願いします。


D  2009-03-21 00:47:39  No: 33445

doscommand_tk.zipの方でやってみました。

Perl側。

#!/usr/local/bin/perl

$| = 1;
print "Start\n";
while(<>){
  if ($_ eq "\x1A\n") { last; }
  print $_;
}
print "End\n";

exit;

ループ中で\x1A\nを検出したらループを抜けるようします。
多分これで望みの結果が得られるのではないかなと。

ちなみにDosCommand1.SendLine(#$1A, True);だと\x1Aだけでなく改行も一緒に送ってるようなので\x1A\nで判定しています。
Perlは良く分からないのでこのあたりもっとうまく実装してみてください。


kuze  2009-03-21 16:02:07  No: 33446

Dさんへ
検討ありがとうございます。
doscommand_tk.zipで上手くいきますか?
私のほうの挙動と違いますね。
Delphiの方のコードはどうしていますか?
while Cmd.IsRunning do begin
の、IsRunning プロパティは存在しないので、
Activeを使っているのですか?


D  2009-03-22 02:02:40  No: 33447

>Delphiの方のコードはどうしていますか?
  最初に久世さんが示されたコードのままと思います。
コンポーネントはインストールしてフォームに貼り付けました。
コンソールアプリではないので while〜 は使っていません。
DosCommand1のプロパティはInputToOutputはTrueにしましたがFalseでも動作に支障はありませんでした。
というかどのプロパティをいじっても動作に違いは出ますが支障はありませんでした。

Perl側で0x1Aがきたらループを抜けるという処理を足しただけです。

Delphi側。

unit main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, DosCommand;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Edit2: TEdit;
    Memo1: TMemo;
    DosCommand1: TDosCommand;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Lines.Add('perl.exe test.pl');
  DosCommand1.CommandLine := 'perl.exe test.pl';
  DosCommand1.Execute;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  DosCommand1.SendLine(Edit2.Text, True);
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  DosCommand1.SendLine(#$1A, True);
end;

procedure TForm1.FormShow(Sender: TObject);
begin
  DosCommand1.OutputLines := Memo1.Lines;
end;

end.

ちなみにPerlでは0x1Aを受け取ってもそれをファイルの終わりとみなすことはなく単なるデータとしての扱いにしかならないようです。
なのでループを抜ける条件は0x1Aに限らず、例えば0x0でも0x1でもあるいは"END"という文字列であっても、Delphi側とPerl側で申し合わせていれば何でもOKという感じです。
TOBYさんが2chで得た情報のとおりです。

またコンソールでCtrl+ZとしてもPerlに0x1Aは送られないようです。

#!/usr/local/bin/perl

$| = 1;
print "Start\n";
while(<>){
  if (index($_, "\x1A") > -1) {
    print "--- EOF ---\n";
    last;
  }
  print $_;
}
print "End\n";

exit;

このようにした場合Button3を押して#$1Aを送った場合やテキストファイルの途中をバイナリエディタで0x1Aに書き換えたものをリダイレクトさせた場合"--- EOF ---"は出力されますがコンソールでCtrl+Zとした場合は出力されません。
素人考えなんですがコンソールからCtrl+Zとした場合0x1Aを送ってファイルの終わりとしているのではなく別のやり方でファイルの終わりとしているのではないかなと思いました。


kuze  2009-03-22 07:06:25  No: 33448

TOBYさん、Dさん、ありがとうございました。
Perlの方を少し手直しする必要があるので、理想形ではないですが
目的は達成できるので、解決としたいと思います。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加