Checking Standard Input in C#

2019-03-12 18:14发布

问题:

I'm writing a small command line utility whose purpose is to parse the output of another utility. I want it to be invokable in two ways:

c:\> myutility filewithoutput.txt

Or,

c:\> otherutility -args | myutility

So, basically, standard in or a file argument. My first attempt at this looked like this:

TextReader reader;

if (args.Length > 1) {
    reader = new StreamReader(new FileStream(args[1], FileMode.Open));
} else {
    reader = Console.In;
}

Process(reader);

The file argument works fine, and piping the output from the utility to my utility works fine, but if you just invoke it normally (no arguments and no piped data), it hangs. Or, rather, it blocks on waiting to read from standard in.

My second draft looked like this:

TextReader reader;

if (args.Length > 1) {
    reader = new StreamReader(new FileStream(args[1], FileMode.Open));
} else {
    if(Console.KeyAvailable) {
        reader = Console.In;
    } else {
        Console.WriteLine("Error, need data");
        return;
    }
}

Process(reader);

While KeyAvailable fixes the "no input" problem, it throws an exception if you try to pipe in data >_<

Unhandled Exception: System.InvalidOperationException: Cannot see if a key
has been pressed when either application does not have a console or when
console input has been redirected from a file. Try Console.In.Peek.

at System.Console.get_KeyAvailable()
at MyUtility.Program.Main(String[] args) in Program.cs:line 39

The exception suggests I use Console.In.Peek, so my next draft is as such:

TextReader reader;

if (args.Length > 1) {
    reader = new StreamReader(new FileStream(args[1], FileMode.Open));
} else {
    if(Console.In.Peek() != 0) {
        reader = Console.In;
    } else {
        Console.WriteLine("Error, need data");
        return;
    }
}

Process(reader);

However, this has the same problem as the first try: It blocks, looking for input. Argh!

Is there something I'm missing?

Sidenote: I am aware of the convention of the argument "-" meaning "use standard input". I will use that if there's no other way. But, surely there's got to be some way of detecting if standard in is the console or not!

Edit: Here's the final version that seems to do what I need:

TextReader reader;

if (args.Length > 1) {
    reader = new StreamReader(new FileStream(args[1], FileMode.Open));
} else {
    try {
        bool tmp = Console.KeyAvailable;
        Console.WriteLine("Error, need data");
        return;
    } catch(InvalidOperationException) {
        reader = Console.In;
    }
}

Process(reader);

Not a big fan of using Exceptions for flow like that, but... eh.

回答1:

I've been using Pieter's solution for a while until I realised it doesn't work in Mono. Mono doesn't throw an exception when retrieving Console.KeyAvailable with piped input, so that approach doesn't help.

However, as of .NET 4.5, Console actually provides a new field IsInputRedirected which makes this a lot simpler, removes the obscurity and the unnecessary try/catch:

TextReader reader;

if (args.Length > 1) {
    reader = new StreamReader(new FileStream(args[1], FileMode.Open));
} else {
    if (Console.IsInputRedirected) {
        reader = Console.In;
    } else {
        Console.WriteLine("Error, need data");
        return;
    }
}

Process(reader);


回答2:

The quick and dirty way is to wrap Console.KeyAvailable in a try/catch and if that throws, you know that input is redirected from a file. It's not very unusual to use try/catch to detect a state when you cannot find an appropriate method to do the checking for you.



回答3:

Looks like you should be able to use some Windows API calls to determine that. Hans Passant's answer even has a helper class to wrap it all up.