8

I have the following code:

int main()
{
    char str[] = "Hello\n";
    write(0, str, 6);   // write() to STDIN
    return 0;
}

When I compiled and executed this program, Hello was printed in the terminal.

Why did it work? Did write() replace my 0 (STDIN) argument with 1 (STDOUT)?

jsageryd
  • 4,234
  • 21
  • 34
Tom
  • 1,344
  • 9
  • 27

3 Answers3

14

Well, old Unix systems were originaly used with serial terminals, and a special program getty was in charge to manage the serial devices, open and configure them, display a message on an incoming connexion (break signal), and pass the opened file descriptors to login and then the shell.

It used to open the tty device as input/output to configure it, and that was then duplicated in file descriptors 0, 1 and 2. And by default the (still good old) stty command operates by default on standard input. To ensure compatibility, on modern Linuxes, when you are connected to a terminal, file descriptor 0 is still opened for input/output.

It can be used as a quick and dirty hack to only display prompts when standard input is connected to a terminal, because if standard input is redirected to a read only file or pipe, all writes will fail (without any harm for the process) and nothing will be printed. But it is anyway a dirty hack: just imagine what happens if a caller passes a file opened for input/output as standard input... That's why good practices recommend to use stderr for prompts or messages to avoid having them lost in redirected stream while keeping output and input in separate streams, which is neither harder nor longer.

TL/DR: if you are connected to a terminal, standard input is opened for input/output even if the name and standard usage could suggest it is read only.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • Perhaps it would be better to not suggest such a hack, since `stderr` is available for that exact purpose? Many do not seem to know it. (Besides, one can call `ctermid()` to find the path to the current controlling (pseudo-)terminal, and try and open that, to write to terminal.) As a side note, POSIX.1-2001 and later state that [stderr is expected to be opened for reading and writing](http://pubs.opengroup.org/onlinepubs/009695399/functions/stdin.html), because in most situations, it should be connected to the controlling terminal (if there is any), even if stdout and/or stdin are redirected. – Nominal Animal Jun 15 '16 at 13:49
  • @NominalAnimal: that's why I said it is a dirty hack and even give a use case where it could lead to terrible results... – Serge Ballesta Jun 15 '16 at 16:16
  • 1
    Yes. It's just that most people seem surprised by my example code having `fprintf(stderr, ...)` scattered everywhere, outputting human-readable information intended for the user (as opposed to stdin/stdout used for *data*). I wish more people would suggest learners to use stderr for human-readable diagnostics, and stdin/stdout primarily for data. Learners often think *"hacks are cool"*, and often do not latch on better practices. You're cool, so you could set a better example. ;) – Nominal Animal Jun 15 '16 at 16:26
  • @NominalAnimal: Does it look better now? – Serge Ballesta Jun 15 '16 at 16:32
7

Because by default your terminal will echo stdin back out to the console. Try redirecting it to a file; it didn't actually write to stdout.

mpontillo
  • 13,559
  • 7
  • 62
  • 90
  • I have tested your answer with expanded OP's code, redirection and piping, you're right, @Mike . Yet it makes me wonder, what's the use of such programmatic writing to `stdin`? Can the content written in such a way also get retrieved from the `stdin` buffer somehow? – user3078414 Jun 14 '16 at 22:08
  • 1
    @user3078414, as far as I understand it, it's a bug. Don't do this. ;-) – mpontillo Jun 14 '16 at 22:17
  • I wouldn't do it in real life, unless recommended practice @Mike. Just for test, this doesn't work: `#include #include int main() { char str[] = "Hello, World!\n"; char text[100]; write(0, str, strlen(str)); read(0, text, strlen(str)); write(1, text, strlen(text)); return 0; }` (-: – user3078414 Jun 14 '16 at 22:25
  • 1
    @user3078414: It is because terminals (actually [pseudoterminals](http://man7.org/linux/man-pages/man7/pty.7.html) aka `pty`s in Linux) are really full duplex devices, and in this case, standard input is connected to one. The [`isatty()`](http://man7.org/linux/man-pages/man3/isatty.3.html) function will tell you if a descriptor is connected to a pseudoterminal. Libraries like [ncurses](https://en.wikipedia.org/wiki/Ncurses) write control messages to the standard input, and read the responses from the terminal master (kernel or program) interspersed among other input. – Nominal Animal Jun 15 '16 at 00:14
  • 1
    As an example, you can run `printf 'Foo\033[6nBar\n'` (using any POSIXy shell like dash or bash) in a terminal window. You'll see `FooBar` printed on its own line, but with garbage you can edit already present on the next command line. The already-present garbage probably ends with `;4R`, since the command, [DSR](https://en.wikipedia.org/wiki/ANSI_escape_code), yields a response `\033[` *row* `;` *column* `R`, and at the point of the command the terminal cursor is in the fourth column. – Nominal Animal Jun 15 '16 at 00:25
0

Are you confusing write with fwrite? The first parameter in write is a "file descripter", but it's not stdin. Try doing an fwrite to stdin -- it doesn't happen.

detourvmx
  • 1
  • 1