15

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.

Marc Mutz - mmutz
  • 24,485
  • 12
  • 80
  • 90
Mike Caron
  • 14,351
  • 4
  • 49
  • 77
  • Is this relevant? http://efreedom.com/Question/1-3453220/Detect-ConsoleIn-Stdin-Redirected – bzlm Oct 18 '10 at 17:17
  • 2
    @bzlm - why not link directly to the source (which happens to be Stack Overflow): http://stackoverflow.com/questions/3453220 – John Rasch Oct 18 '10 at 17:21
  • @John Oh, so it's *my* fault that SO doesn't rank highest for the search terms I entered. :) – bzlm Oct 18 '10 at 17:22
  • @bzlm - definitely the SE's fault not yours! – John Rasch Oct 18 '10 at 17:25
  • I will go with Pieter's answer, since it is likely more portable (Not that portability is a factor, but you never know). However, good catch. – Mike Caron Oct 18 '10 at 17:34
  • in unix land the hanging behavior is what's expected; all CLIs do it – pm100 Oct 18 '10 at 17:40
  • @Mike What would you like to happen when your program is invoked without arguments? Just as @pm100 says, I would expect it to read interactively. – bzlm Oct 18 '10 at 17:48
  • @bzlm As shown by my code, I expect it to go "Hey, dummy, you can't invoke me by double clicking". The data its processing is not really feasible to input by hand (although, I suppose you could if you were sufficiently bored.) – Mike Caron Oct 18 '10 at 17:57

3 Answers3

10

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 van Ginkel
  • 29,160
  • 8
  • 71
  • 111
  • ... 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 Oct 18 '10 at 17:33
  • Unfortunately, this doesn't seem to be working in Mono, [but as of .NET 4.5 there's a simpler solution](http://stackoverflow.com/a/34253814/1633117). – Martin Ender Dec 13 '15 at 17:09
6

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);
Martin Ender
  • 43,427
  • 11
  • 90
  • 130
1

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.

Community
  • 1
  • 1
Don Kirkby
  • 53,582
  • 27
  • 205
  • 286