8

I just noticed that bash interactive shell by default writes the prompt and echoes anything you type to file descriptor 2 (stderr). This can be verified by running bash inside strace.

What is the reason for this? Why does it not write these things to stdout?

6
  • You could do something like exec > out.txt, and then enter any commands you like with their output redirected, but still seeing the errors and the prompt. Maybe there's some remotely sensible use-case for this.
    – ilkkachu
    Commented Jul 21, 2017 at 20:55
  • So if you type while a program is running those characters do not end up mixed in with the output of the program. Having them mixed with stderr is not ideal but stdout would be worse. Commented Jul 21, 2017 at 21:11
  • @MarkWagner, well, no, since if you have a (foreground) job running, the input goes to it (bar terminal buffering, and assuming the process actually reads the input). The shell doesn't get the input in that case.
    – ilkkachu
    Commented Jul 21, 2017 at 21:22
  • @MarkWagner I don't buy that as the reason. By default both the shell and the program have the current tty as stdout and stderr; there's nothing preventing them from mixing together the way things are by default. Commented Jul 21, 2017 at 23:35
  • @lkkachu I guess I should have added: in the case where the program is non-interactive. Commented Jul 21, 2017 at 23:45

3 Answers 3

7

The original UNIX versions 5-7 can be seen doing the same thing. (UFD output = 2; http://minnie.tuhs.org/cgi-bin/utree.pl?file=V7/usr/src/cmd/sh/main‌​.c)

time is/was a builtin, and it's definitely useful that it outputs to stderr. However time is not one of the builtins in V5.

I don't think there were any big reasons not to write terminal output to stderr. Output from the shell was either clearly an error message, or output intended for interactive terminals only. It was not necessary for redirecting stdout to redirect the interactive output.

Although stderr was introduced in V6, not V5, in V5 sh manually dup()s stdout to FD 2 after closing the old FD 2 if necessary. It seems they already found a need to print error messages e.g. if exec() failed when trying to launch a command like foo > output.

Now pay attention to how compact the historical unix code is. It is deliberately kept short, because there wasn't necessarily very much physical RAM.

There is a single prs() function to print strings. It does not take an FD parameter. It prints error messages to FD 2, and the other strings are also OK to print to FD 2, so it simply prints to FD 2 unconditionally. Short code, that compiles to few instructions, hence using minimal RAM.

And once things are around for a while, changing them always has the risk of breaking more things than it improves.

What puzzles me is why the developers of python even noticed this and copied it - IMO it's a pretty obscure fact. Maybe it implies an additional reason that I haven't found.

err(s)
char *s;
{

    prs(s);
    prs("\n");
    if(promp == 0) {
        seek(0, 0, 2);
        exit();
    }
}

prs(as)
char *as;
{
    register char *s;

    s = as;
    while(*s)
        putc(*s++);
}

putc(c)
{

    write(2, &c, 1);
}
1
  • 1
    Interesting that it's been like this for a long time. I'm still wondering why. Commented Jul 21, 2017 at 23:37
4

A shell is interactive when it's not interpreting a (possibly inline with -c) script and its stdin is a terminal (but see below for POSIX shells)

In that case, what we want is for the prompt (and the echo of what you type for shells that have their own line editor) to be displayed on the same terminal. The problem is stdin is not guaranteed to be open in read+write mode, so outputting the prompt on stdin would not be reliable. Also midway through the interactive session, one may want to do exec < other-file which would also break the prompt display.

A sensible thing to do and what zsh does would be to determine what terminal that is (using ttyname()) and reopen it for read+write (on a dedicated, separate fd above 10) for user interaction. Using a file descriptor open on /dev/tty (the controlling terminal, which in the great majority of cases will be refering to the same terminal as the one on stdin if stdin is a terminal) would make sense to me but it doesn't seem any shell does it (possibly to take care of the cases where there's no controlling terminal which can happen in some cases like recovery shells on the console).

However the POSIX specification does say that a shell is only interactive if on top the requirements described above, stderr is also a terminal, and POSIX requires the prompts to be written on stderr.

bash aims at POSIX conformance so has to follow those requirements.

I suppose the reason is historical. The POSIX sh specification is based on ksh88 and that's how ksh88 behaved and the Bourne shell before it. (even though ttyname() already existed in Unix V7 where the Bourne shell was first released).

It's common for user interaction for terminal applications (like the prompts of rm/mv/find -ok...) to be on stdin+stderr. stderr would be open in write mode where open, and in terminal sessions would point to the terminal. stdout is for the command's normal output, it's important to keep it separate from user interaction messages so one can use redirection to store or post-process the normal output. Having it on stderr (a known in advance specific fd) instead of an internal fd open on /dev/tty makes it easier to remove those messages (by running the command with 2> /dev/null for instance) or use the command non-interactively.

In the case of a shell though, it is not particularly useful. Running sh 2> /dev/null makes the shell non-interactive (which means prompts are not displayed but has many other side effects). That means one can disable the prompts with exec 2> /dev/null, but that would have the side effect of also discarding all commands errors unless you run each command with cmd 2> something. Emptying PS1, PS2, PS3, PS4 would be much better.

That allows user input to come from one terminal and output to go to a different terminal, but I don't see why anyone would want to do that.

A possible reason is that it would be more foolproof:

  • /dev/tty as seen above may not work in corner cases where there's no controlling terminal,
  • ttyname() may not work when /dev is not properly populated or when using chroots.

Note that it's worse in some other shells. csh, tcsh, rc, es, akanga, fish display the prompt and the echo of what you type on stdout (though for fish not the prompt if stdout is not a terminal, and csh which doesn't have a line-editor doesn't output any echo (that is taken care of by the tty line discipline of the terminal device)).

2
  • 1
    "may not work when /dev is not properly populated". Obscure fact discovered while researching the question: PDP-7 UNIX (the one before V1) required a tty node in every directory. "There was no /dev directory (because no path names)".
    – sourcejedi
    Commented Jul 22, 2017 at 11:17
  • @sourcejedi, I'm not sure it's worth considering pre-V7 Unices as the Bourne shell was radically different from its predecessors so would have been free to do things differently in that regard. Thanks for that fact about PDP-7 Unix though (and for the ptsname typo). Commented Jul 22, 2017 at 11:28
0

I read the previous answers like this: Because bash mimics the behaviour of its predecessors, mainly the Bourne and Korn shells. "It has always been like that" and everybody just accepted it as it is, nobody questioned the status quo. Sorry, but that sucks.

IMO, the real reason is that stdout is subject to redirection because of the way command line tools (including shell scripts) typically work in the *ix shells, using pipes to pass output from one process as input to the other. In that case, the prompt would not be seen and just become part of the piped data--not what you want.

1
  • 2
    Ah, but if you want to go down that route, note that the question is about interactive shells. One doesn't typically redirect the output of the shell itself when using it interactively.
    – muru
    Commented Nov 30, 2023 at 8:44

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .