2014年09月03日

C#で標準入力と標準出力のリダイレクト

   このエントリーをはてなブックマークに追加 Clip to Evernote
どう書く?orgに感謝を込めて」シリーズ その38

■問題 (出題者:にしお さん)
以下のようなプログラムを作ってください。

       * コマンドライン引数を二つ取り、引数で指定されたプログラムを起動する(以下A, B)
       * Aの標準出力をBの標準入力へ、Bの標準出力をAの標準入力へ中継する。
       * A, Bどちらかが終了した場合はもう片方を終了して自身も終了する。

この問題は、標準入力と標準出力のリダイレクトをどうやるかという問題ですね。
ただ、プログラムA,Bとも標準入力待ちから始まると、いつまでたっても待ち状態の ままになってしまうので、どんなプログラムでもOKというわけじゃないですね(たぶん)...
なので、ここでは起動するプログラムA,Bも自作することとしました。

Aは単に標準入力を()で囲んで標準出力に複写することを永遠に続けるだけのプログラム。
Bは最初に文字列を標準出力に出力後に、標準入力の文字列を加工をし、標準出力に出力することを3回繰り返すプログラム。
ただし、これだけだと、それぞれのの出力を食い合って、何をやっているかを確認できないので、 標準エラーにもその内容を出力するようにしています。
つまらない例ですみません m(_ _)m

C#では、プロセスを起動する際のオプションであるProcessStartInfo の RedirectStandardInput、RedirectStandardOutput を true にすることで、リダイレクトが可能になります。

■プログラムA
using System;

namespace Doukaku.Org {
    class ProgramA {
        static void Main(string[] args) {
            while (true) {
                string s = Console.ReadLine();
                var t = string.Format("({0})", s);
                Console.Error.WriteLine("A > " + t);
                Console.WriteLine(t);
            }
        }
    }
}

■プログラムB
using System;

namespace Doukaku.Org {
    class ProgramB {
        static void Main(string[] args) {
            string o = "文字数は?";
            Console.WriteLine(o);
            Console.Error.WriteLine("B > " + o);
            for (int i = 0; i < 3; i++) {
                string s = Console.ReadLine();
                o = string.Format("{0}:{1}文字", s , s.Length);
                Console.WriteLine(o);
                Console.Error.WriteLine("B > " + o);
             }
        }
    }
}

■C#で書いたA,Bを起動するプログラム
using System;
using System.Diagnostics;
using System.Threading;

namespace Doukaku.Org {
    class Program {
        static void Main(string[] args) {
            var pa = ProcessStart("A.exe");
            var pb = ProcessStart("B.exe");
            Relay(pa, pb);
            Relay(pb, pa);
            do {
                pa.WaitForExit(500);
                pb.WaitForExit(500);
            } while ((pa.HasExited & pb.HasExited) == true);
            if (pa.HasExited == true && pb.HasExited == false)
                pb.Kill();
            if (pa.HasExited == false && pb.HasExited == true)
                pa.Kill();
            Console.WriteLine("END");
        }

        static Thread Relay(Process outp, Process inp) {
            Thread thread = new Thread(() => {
                while (outp.StandardOutput.EndOfStream == false) {
                    inp.StandardInput.WriteLine(outp.StandardOutput.ReadLine());
                };
            });
            thread.Start();
            return thread;
        }

        static Process ProcessStart(string cmd) {
            Process process = new Process();
            ProcessStartInfo psi = new ProcessStartInfo(cmd);
            psi.UseShellExecute = false;
            psi.RedirectStandardInput = true;
            psi.RedirectStandardOutput = true;
            process.StartInfo = psi;
            process.Start();
            return process;
        }
    }
}

これを起動すると、以下のような結果が得られます。

B > 文字数は?
A > (文字数は?)
B > (文字数は?):7文字
A > ((文字数は?):7文字)
B > ((文字数は?):7文字):13文字
A > (((文字数は?):7文字):13文字)
B > (((文字数は?):7文字):13文字):20文字
A > ((((文字数は?):7文字):13文字):20文字)
END

  

Posted by gushwell at 22:30Comments(0)TrackBack(0)

2014年06月18日

C#で制限時間内のキー入力検査

   このエントリーをはてなブックマークに追加 Clip to Evernote
どう書く?orgに感謝を込めて」シリーズ その17

■問題 (出題者:raynstardさん)
ユーザーの入力を検査する関数 InputCheckerを作成して 3回連続実行してください。
関数 InputCheckerは、以下の仕様を満たしてください。

     1. 引数に 時間(秒) Nと文字列 Sを受け取ること。
     2. ユーザからの標準入力とあらかじめ指定された S を比較し、 一致すれば「OK」、
         一致しなければ「NG」を標準出力へ出力すること。
     3. ユーザーの入力がN秒以内に完了しなかった場合は、比較せず 「TIME OUT」を標準出力へ
         出力すること。
     4. 経過時間はユーザーが入力を開始した地点から計測すること。
     5. ENTER キーの入力によってユーザー入力が完了したと仮定すること。
     6. ユーザの入力が完了するまでは、完了を待ち続けること

たとえば、「InputCheker(5, "ABCDEF")」と指定した場合、 出力例はこんな感じです。

     1. input(ABCDEF) =>ABCDEF<ENTER>

          1. result => OK
          2. result => NG
          3. result => TIME OUT


1. input(ABCDEF) =>
と出力して入力待ちをし、ユーザーが「ABCDEF<ENTER>」を入力したとき、 入力開始から5秒以内ならば「OK」、5秒をこえていれば「TIME OUT」を出力します。 このとき、ユーザーがキーを押下しなければ1. を出力してから たとえ10秒たっていても「TIME OUT」にはならないので注意してください。 時間計測はあくまでユーザーが入力を開始してからです。

問題文が長いので、面倒そうな印象を受けますが、実際に書いてみるとそれほどでもないです。
「ユーザーが入力を開始した地点」というのをどうやって調べるかがこのプログラムの肝になりますが、Console.KeyAvailable プロパティを使えば簡単に分かります。


■C#で書いたコード

using System;
using System.Threading;

namespace Doukaku.Org {
    class Program {
        // きれいなコードじゃないけど...
        static void InputCheker(int n, string s) {
            Console.Write("input({0}) => ", s);
            while (!Console.KeyAvailable)
                Thread.Sleep(100);
            DateTime now = DateTime.Now;
            string input = Console.ReadLine();
            string result = (DateTime.Now <= now.AddSeconds(n))
                ? s == input ? "OK" : "NG"
                : "TIME OUT";
            Console.WriteLine("result => {0}",result);
        }
        static void Main(string[] args) {
            InputCheker(5, "ABCDEF");
            InputCheker(4, "Microsoft");
            InputCheker(3, "csharp");
            Console.ReadLine();
        }
    }
}
  
Posted by gushwell at 22:30Comments(0)TrackBack(0)

2010年11月23日

コンソールで処理中であることを示す文字を表示

   このエントリーをはてなブックマークに追加 Clip to Evernote
元ネタ いろいろ備忘録日記 - コンソールで処理中であることを示す文字を表示

おもしろそうだったので、僕もやってみました。

指定した複数の値を繰り返す」で示したRepeat拡張メソッドを使って、gsf_zero1 さんが書いたコードを書き換えています。

以下に示すコードのProgressメソッドが、処理中であることを示すメソッドです。
gsf_zero1 さんは、ラムダ式としていましたが、これをメソッドにしています。

このメソッドの中で、Repeatメソッドを使い、コンソールに表示する文字を繰り返し取得しています。
僕のコードでは、'.' 'o' 'O' '@' 'O' 'o' という6文字を繰り返して取り出し、コンソールに表示しています。
別の文字を表示したいなら、文字列 ".oO@Oo" の部分を"|/-" など別のものに置き換えるだけです。


  
Posted by gushwell at 23:10Comments(0)TrackBack(0)