June 11, 2013
On 2013-06-11 12:55, Nick Sabalausky wrote:

> I think I remember him saying somewhere that he uses zsh, although I
> can't look it up or test it on zsh at the moment.

With zsh and the first one I get:

cat: stdout: Bad file descriptor

With the second one I get nothing and 0 as the exit code.

-- 
/Jacob Carlborg
June 11, 2013
On 6/11/13 3:09 AM, Jacob Carlborg wrote:
> On 2013-06-11 08:38, Nick Sabalausky wrote:
>
>> I just tried both on Debian 6:
>>
>> nick@debian6:~$ cat 1< /dev/null
>> cfws
>> cat: write error: Bad file descriptor
>> nick@debian6:~$ echo meh 1< /dev/null
>> bash: echo: write error: Bad file descriptor
>>
>> Maybe OSX behaves differently?
>
> I get the same on Mac OS X 10.6.3 using bash. Is Andrei perhaps using
> another shell?

I use zsh. Indeed under bash I can reproduce the failure. But if I run the printf-based test, it exits successfully.

Andrei


June 11, 2013
On Tue, 11 Jun 2013 13:17:13 +0200
"estew" <estewh@gmail.com> wrote:

> On Tuesday, 11 June 2013 at 10:55:16 UTC, Nick Sabalausky wrote:
> > On Tue, 11 Jun 2013 09:09:43 +0200
> > Jacob Carlborg <doob@me.com> wrote:
> >
> >> On 2013-06-11 08:38, Nick Sabalausky wrote:
> >> 
> >> > I just tried both on Debian 6:
> >> >
> >> > nick@debian6:~$ cat 1< /dev/null
> >> > cfws
> >> > cat: write error: Bad file descriptor
> >> > nick@debian6:~$ echo meh 1< /dev/null
> >> > bash: echo: write error: Bad file descriptor
> >> >
> >> > Maybe OSX behaves differently?
> >> 
> >> I get the same on Mac OS X 10.6.3 using bash. Is Andrei perhaps using another shell?
> >> 
> >
> > I think I remember him saying somewhere that he uses zsh,
> > although I
> > can't look it up or test it on zsh at the moment.
> 
> Just tried zsh, bash, sh..
> 
> $ echo meh 1< /dev/null
> zsh: no output
> bash: Bad file descriptor
> sh: Bad file descriptor
> 
> $ echo meh 2< /dev/null
> zsh: meh
> bash: meh
> sh: meh
> 

Hmm, yea, sounds like it may not have been the best test in the first place after all if even the shells and stdout-vs-stderr don't all agree with each other.

I'm no unix expert, but wouldn't 1</dev/null just mean "If the program tries to read input from stdout, feed it input from /dev/null instead"? Or is that really a known way to force stdout to be read-only?

June 11, 2013
On Tuesday, 11 June 2013 at 12:37:53 UTC, Andrei Alexandrescu wrote:
> On 6/11/13 3:09 AM, Jacob Carlborg wrote:
>> On 2013-06-11 08:38, Nick Sabalausky wrote:
>>
>>> I just tried both on Debian 6:
>>>
>>> nick@debian6:~$ cat 1< /dev/null
>>> cfws
>>> cat: write error: Bad file descriptor
>>> nick@debian6:~$ echo meh 1< /dev/null
>>> bash: echo: write error: Bad file descriptor
>>>
>>> Maybe OSX behaves differently?
>>
>> I get the same on Mac OS X 10.6.3 using bash. Is Andrei perhaps using
>> another shell?
>
> I use zsh. Indeed under bash I can reproduce the failure. But if I run the printf-based test, it exits successfully.
>
> Andrei

This is a good point, I kind of wondered why bash was the one reporting echo failed.
June 11, 2013
On Tue, 11 Jun 2013 01:37:52 -0400, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:

> On 6/11/13 12:04 AM, David Nadlinger wrote:
>> On Tuesday, 11 June 2013 at 04:02:59 UTC, Andrei Alexandrescu wrote:
>>> The test program is flawed; writeln() writes to stdout, and the
>>> redirection is to stdin.
>>
>> Wouldn't stdin be fd 0?
>>
>> David
>
> Oh indeed my bad. But the test is still flawed. Consider:
>
> import std.stdio;
> int main()
> {
>      return printf("test\n") < 0;
> }
>
> This should return 1 if printf fails. It succeeds for 1</dev/null.

This actually gave me an idea of why it doesn't fail.

This code DOES fail:

import std.stdio;

int main()
{
    writeln("hello");
    std.stdio.stdout.flush();
    return 0;
}

Here is the (interesting) issue:

FILE *, and likewise std.stdio.File, which uses FILE * as it's implementation, is a buffered stream.

An interesting problem to solve for buffered streams is when to flush the buffer.  The most efficient thing to do is to wait until the entire buffer is full, then flush it (call the syscall to write the data to the handle/descriptor).  However, in an INTERACTIVE console, you want to flush more frequently, in case the data is coming out sporadically.

HOWEVER, the C runtime first checks to see if the file descriptor is a console.  If it is, it sets the flag indicating that it should flush on newlines, otherwise, it does not.  Instead of seeing 4K (or whatever buffer size is) of data at a time, the user sees 1 line at a time, much more parseable by a human.  This is why cat'ing a file to another file is much more efficient than cat'ing it to the console.

Important to note is that /dev/null is NOT a console :)

So what is happening is, writeln("hello") is writing "hello\n" to the FILE *, which sits in the buffer, no flushing occurs, because /dev/null is not a console.  However, upon calling flush, it fails, because the file descriptor is invalid.

Upon exit, std.stdio.stdout relies on core.stdc.stdio.stdout to flush the data (in fact, I don't even think stdout's dtor is called).  And C's stdout does not throw an exception or alter the exit code.  This is why you have no visible errors.

If D did NOT rely on C's FILE *, it may have a chance to throw an exception on program exit (but likely wouldn't because the buffer is likely GC based and therefore can't be accessed on the dtor :)

Re: different shells behaving differently, depending on how the individual shells are implemented can affect how this plays out.

-Steve
June 11, 2013
On 6/11/13 11:57 AM, Steven Schveighoffer wrote:
> This code DOES fail:
>
> import std.stdio;
>
> int main()
> {
> writeln("hello");
> std.stdio.stdout.flush();
> return 0;
> }

Ah, I suspected so. (At a point in D's history writeln() did do a flush; people wanted to eliminate it for efficiency reasons.)

We could introduce a flush() with throw in std.stdiobase.


Andrei
June 11, 2013
On Tuesday, 11 June 2013 at 16:50:50 UTC, Andrei Alexandrescu wrote:
> On 6/11/13 11:57 AM, Steven Schveighoffer wrote:
>> This code DOES fail:
>>
>> import std.stdio;
>>
>> int main()
>> {
>> writeln("hello");
>> std.stdio.stdout.flush();
>> return 0;
>> }
>
> Ah, I suspected so. (At a point in D's history writeln() did do a flush; people wanted to eliminate it for efficiency reasons.)
>
> We could introduce a flush() with throw in std.stdiobase.

As in a module destructor?  Isn't it better to let the error pass silently rather than throwing an exception that can't be caught?
June 11, 2013
On Tue, 11 Jun 2013 13:01:41 -0400, Lars T. Kyllingstad <public@kyllingen.net> wrote:

> On Tuesday, 11 June 2013 at 16:50:50 UTC, Andrei Alexandrescu wrote:
>> On 6/11/13 11:57 AM, Steven Schveighoffer wrote:
>>> This code DOES fail:
>>>
>>> import std.stdio;
>>>
>>> int main()
>>> {
>>> writeln("hello");
>>> std.stdio.stdout.flush();
>>> return 0;
>>> }
>>
>> Ah, I suspected so. (At a point in D's history writeln() did do a flush; people wanted to eliminate it for efficiency reasons.)
>>
>> We could introduce a flush() with throw in std.stdiobase.
>
> As in a module destructor?  Isn't it better to let the error pass silently rather than throwing an exception that can't be caught?

I agree.  This should not be a failure at that point.

If you want to induce and catch failure, put std.stdio.stdout.flush() at the end of your main function, like I did.

-Steve
June 11, 2013
On Tuesday, 11 June 2013 at 17:01:43 UTC, Lars T. Kyllingstad wrote:
>> Ah, I suspected so. (At a point in D's history writeln() did do a flush; people wanted to eliminate it for efficiency reasons.)
>>
>> We could introduce a flush() with throw in std.stdiobase.
>
> As in a module destructor?  Isn't it better to let the error pass silently rather than throwing an exception that can't be caught?

Passing silently on errors would contradict with several talks Andrei and Walter have given about how D's error handling is much better than C/C++ one (there are some HelloWorld program comparisions from those languages available on YT), I think they want that claim to remain valid. To me it is important to have error reported whenever something fails and have it as fast as possible - image a db backup system which fails silently.
June 11, 2013
On 6/11/13 1:01 PM, Lars T. Kyllingstad wrote:
> On Tuesday, 11 June 2013 at 16:50:50 UTC, Andrei Alexandrescu wrote:
>> On 6/11/13 11:57 AM, Steven Schveighoffer wrote:
>>> This code DOES fail:
>>>
>>> import std.stdio;
>>>
>>> int main()
>>> {
>>> writeln("hello");
>>> std.stdio.stdout.flush();
>>> return 0;
>>> }
>>
>> Ah, I suspected so. (At a point in D's history writeln() did do a
>> flush; people wanted to eliminate it for efficiency reasons.)
>>
>> We could introduce a flush() with throw in std.stdiobase.
>
> As in a module destructor? Isn't it better to let the error pass
> silently rather than throwing an exception that can't be caught?

It will not be caught but will cause the entire program to print a diagnostic and exit with a nonzero error code, which is useful.

Andrei