I/O-Redirection:Console.In terminates unexpectedly

M

mabra

Hi All !

Problem:Reading/duplicating "Console.In" fails for unknown reason.

I wrote a little console helper tool, named TEE, which just duplicates
the StdIn to the StdOut AND an additional stream. Looks working
basically and the part that does this is like the following:

private static void CopyStream(TextWriter outStream, bool dup)
{
char temp;
while(Console.In.Peek() != -1)
{
temp = Convert.ToChar(Console.In.Read());
outStream.Write(temp);
if(dup) Console.Out.Write(temp);
}
}

I use it like this:

dir /s /b /a:-d d:\Develop | Tee d:\Develop.log

But unexpectedly, the resuling textfile does not contain all text send
by the dir command. And, for each time, the resulting textfile has other
content. This lokks like, the loop, shown above, terminates for unknown
reason at a random point.

I do not know, what is wrong with my code.
Any idea why this happens is really very welcome!

Thanks so far and
best regards,
Manfred
 
B

Barry Kelly

mabra said:
I wrote a little console helper tool, named TEE, which just duplicates
the StdIn to the StdOut AND an additional stream. Looks working
basically and the part that does this is like the following:
But unexpectedly, the resuling textfile does not contain all text send
by the dir command. And, for each time, the resulting textfile has other
content. This lokks like, the loop, shown above, terminates for unknown
reason at a random point.

I do not know, what is wrong with my code.

StreamWriter (usually wrapped around a FileStream) buffers characters
before calling FileStream.Write(). What probably happened is that you
didn't dispose or flush the StreamWriter. Ideally you should put the
StreamWriter inside a using block. This program seems to work well for
me:

---8<---
using System;
using System.IO;

class Program
{
static void Main(string[] args)
{
using (TextWriter writer = File.CreateText(args[0]))
while (Console.In.Peek() != -1)
{
char ch = (char) Console.In.Read();
Console.Write(ch);
writer.Write(ch);
}
}
}
--->8---

-- Barry
 
M

mabra

Hello Barry !

Thanks a lot !
I have modified my calling sequence to this:

TextWriter tw = new StreamWriter(filename);
T.CopyStream(tw, true, ref charCount);
tw.Flush(); /////NEW !!!!
tw.Close();

But even this does not help. BTW, your code has the same behavior like
mine:The output is always different. This happens only on larger
directory trees. While I originally "developed" my program, everything
looked fine :-(

The phenomen does not only exist for the "dir" command, xcopy also.
I re-wrote the proggi in VBScript and this gives excactly the same
result as "dir" itself. But I need it in .net because I am currently
writing some other filters too.

Best regards,
Manfred

Barry said:
mabra said:
I wrote a little console helper tool, named TEE, which just duplicates
the StdIn to the StdOut AND an additional stream. Looks working
basically and the part that does this is like the following:
But unexpectedly, the resuling textfile does not contain all text send
by the dir command. And, for each time, the resulting textfile has other
content. This lokks like, the loop, shown above, terminates for unknown
reason at a random point.

I do not know, what is wrong with my code.

StreamWriter (usually wrapped around a FileStream) buffers characters
before calling FileStream.Write(). What probably happened is that you
didn't dispose or flush the StreamWriter. Ideally you should put the
StreamWriter inside a using block. This program seems to work well for
me:

---8<---
using System;
using System.IO;

class Program
{
static void Main(string[] args)
{
using (TextWriter writer = File.CreateText(args[0]))
while (Console.In.Peek() != -1)
{
char ch = (char) Console.In.Read();
Console.Write(ch);
writer.Write(ch);
}
}
}
--->8---

-- Barry
 
B

Barry Kelly

mabra said:
But even this does not help. BTW, your code has the same behavior like
mine:The output is always different.

I see that now, it appears that TextReader uses different code paths
depending on whether you're reading a whole line, character by character
or in blocks - and the behaviour is different, too. It actually looks
like a bug in the BCL. I need to look into it further.

Here's a working version - it'll perform better too:

---8<---
using System;
using System.IO;

class Program
{
static void Main(string[] args)
{
char[] buffer = new char[1024];
using (TextWriter writer = File.CreateText(args[0]))
{
for (;;)
{
int read = Console.In.ReadBlock(buffer, 0,
buffer.Length);
if (read == 0)
break;

Console.Out.Write(buffer, 0, read);
writer.Write(buffer, 0, read);
}
}
}
}
--->8---

-- Barry
 
M

mabra

Hello Barry,

and much thanks for your investigation.

Your lastest version really get's the whole input!! But I disagree on
your performance statement:The proggi has some strong pauses reading the
input - first, I thought, it hungs!

My posting here was just the attempt to address a problem. I normally
would like to read lines and apply a filter on it, but I found no way to
read a line of text and then determine the readers status, so I came to
the Peek() controlled loop. And I usually try to "echo" the readed line
to provide the flow/feedback of the operation to the user [for long
running operations]. I am missing something like "ReaderIsAtEnd" or
"ReaderClosed()" or a "ReaderDisconnected" event. "Peek()" seems to fail.

If I would - according to my original code snippet posted - use the
following:

string temp2 = Console.In.ReadToEnd();
outStream.Write(temp2);
if(dup) Console.Out.Write(temp2);

This works as expected, except for the feedback and the ability to
filter lines :-(

I just made another attempt:I started a new process and redirected it's
output. But there is no difference and the behaviors is excactly the same.

ProcessStartInfo psi = new ProcessStartInfo("cmd.exe", " /c dir /a:-d /s
/b d:\\develop");
psi.RedirectStandardOutput = true;
Process p = new Process();
p.StartInfo = psi;
p.Start();

do
{
Console.WriteLine(outputReader.ReadLine());
} while (outputReader.Peek() == -1);

This does not work [returns ONE line!!!] and the subprocess keeps
running until I use:

Console.Write(p.StandardOutput.ReadToEnd());

Additionally, with the console, mostly you have to use adiitional
P/Invoke code:Control-C events, logoff, PeekConsoleInput !!!

Some improvements would be really great, this is always work.
I am a System Manager not really a developer ;-)

Thanks so far and
best regards,
Manfred

Barry said:
mabra said:
But even this does not help. BTW, your code has the same behavior like
mine:The output is always different.

I see that now, it appears that TextReader uses different code paths
depending on whether you're reading a whole line, character by character
or in blocks - and the behaviour is different, too. It actually looks
like a bug in the BCL. I need to look into it further.

Here's a working version - it'll perform better too:

---8<---
using System;
using System.IO;

class Program
{
static void Main(string[] args)
{
char[] buffer = new char[1024];
using (TextWriter writer = File.CreateText(args[0]))
{
for (;;)
{
int read = Console.In.ReadBlock(buffer, 0,
buffer.Length);
if (read == 0)
break;

Console.Out.Write(buffer, 0, read);
writer.Write(buffer, 0, read);
}
}
}
}
--->8---

-- Barry
 
M

mabra

Hi Barry and All !

Not best, but works [to my original code snippet]:

string temp2 = null;
while((temp2 = Console.In.ReadLine())!= null) //!!!!!!!!!!
{
outStream.WriteLine(temp2);
if(dup) Console.Out.WriteLine(temp2);
}

Just another idea, found in the wild of the internet ;-)
This works.

Best regards,
Manfred
Hello Barry,

and much thanks for your investigation.

Your lastest version really get's the whole input!! But I disagree on
your performance statement:The proggi has some strong pauses reading the
input - first, I thought, it hungs!

My posting here was just the attempt to address a problem. I normally
would like to read lines and apply a filter on it, but I found no way to
read a line of text and then determine the readers status, so I came to
the Peek() controlled loop. And I usually try to "echo" the readed line
to provide the flow/feedback of the operation to the user [for long
running operations]. I am missing something like "ReaderIsAtEnd" or
"ReaderClosed()" or a "ReaderDisconnected" event. "Peek()" seems to fail.

If I would - according to my original code snippet posted - use the
following:

string temp2 = Console.In.ReadToEnd();
outStream.Write(temp2);
if(dup) Console.Out.Write(temp2);

This works as expected, except for the feedback and the ability to
filter lines :-(

I just made another attempt:I started a new process and redirected it's
output. But there is no difference and the behaviors is excactly the same.

ProcessStartInfo psi = new ProcessStartInfo("cmd.exe", " /c dir /a:-d /s
/b d:\\develop");
psi.RedirectStandardOutput = true;
Process p = new Process();
p.StartInfo = psi;
p.Start();

do
{
Console.WriteLine(outputReader.ReadLine());
} while (outputReader.Peek() == -1);

This does not work [returns ONE line!!!] and the subprocess keeps
running until I use:

Console.Write(p.StandardOutput.ReadToEnd());

Additionally, with the console, mostly you have to use adiitional
P/Invoke code:Control-C events, logoff, PeekConsoleInput !!!

Some improvements would be really great, this is always work.
I am a System Manager not really a developer ;-)

Thanks so far and
best regards,
Manfred

Barry said:
mabra said:
But even this does not help. BTW, your code has the same behavior
like mine:The output is always different.

I see that now, it appears that TextReader uses different code paths
depending on whether you're reading a whole line, character by character
or in blocks - and the behaviour is different, too. It actually looks
like a bug in the BCL. I need to look into it further.

Here's a working version - it'll perform better too:

---8<---
using System;
using System.IO;

class Program
{
static void Main(string[] args)
{
char[] buffer = new char[1024];
using (TextWriter writer = File.CreateText(args[0]))
{
for (;;)
{
int read = Console.In.ReadBlock(buffer, 0,
buffer.Length);
if (read == 0)
break;
Console.Out.Write(buffer, 0, read);
writer.Write(buffer, 0, read);
}
}
}
}
--->8---

-- Barry
 
B

Barry Kelly

mabra said:
string temp2 = null;
while((temp2 = Console.In.ReadLine())!= null) //!!!!!!!!!!
{
outStream.WriteLine(temp2);
if(dup) Console.Out.WriteLine(temp2);
}

Just another idea, found in the wild of the internet ;-)
This works.

It works, but it doesn't print characters as it receives them. Another
thing I'd suggest is constructing the stream reader with a buffer of one
character.

-- Barry
 
M

Manfred Braun

Hello Barry !

Sorry, for being so late :-(

I just wrote a simple app which sends a message to the pipe and waits two
seconds. It sends ten messages. If I redirect the output to the TEE program,
the message is displayed immidiately!

Thanks for the one-byte-buffer suggestion. That might help.
I'l try this out also.

Thanks so far and
best regards,
Manfred
[the same:But from another account]
 
Top