views:

72

answers:

2

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  A: 

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.

Pieter
... I really hate it when answers are so obvious, they fly right by me. Though, I still don't like the fact that there's no built in way to do this...
Mike Caron
A: 

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.

Don Kirkby