11

I am looking at the strace output of a running bash process connected to a terminal, for educational purposes.

My bash process has PID 2883.

I type

[OP@localhost ~]$ strace -e trace=openat,read,write,fork,vfork,clone,execve -p 2883 2> bash.strace

Into a terminal. I then go into my bash process, and have the following interaction:

[OP@localhost ~]$ ls

Looking at the output, I see

strace: Process 2883 attached
read(0, "l", 1)                         = 1
write(2, "l", 1)                        = 1
read(0, "s", 1)                         = 1
write(2, "s", 1)                        = 1
read(0, "\r", 1)                        = 1
write(2, "\n", 1)                       = 1
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fec6b1d8e50) = 3917
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3917, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
write(1, "\33]0;OP@localhost:~\7", 23) = 23
write(2, "[OP@localhost ~]$ ", 22)  = 22
...

I am confused at the last two lines. It appears that bash is attempting to write two shell prompts? What's going on here?

24

The <ESC>]0; sequence (shown as \33]0; by strace) is the escape sequence to set the terminal window title. It's terminated with the BEL character (\7), so the first write sets the window title. The second prints the actual prompt. Note that even apart from the escape sequence, they're not exactly the same. The prompt has a surrounding [..] while the window title doesn't.

We can also see that the first write goes to stdout (fd 1, the first argument to write()), and the second to stderr. Bash prints the prompt to stderr, so the first write comes from somewhere else. That somewhere is probably PROMPT_COMMAND, like the one in Debian's default startup scripts for Bash. There's something like this in there:

case "$TERM" in
xterm*|rxvt*)
    PROMPT_COMMAND='echo -ne "\033]0;${USER}@${HOSTNAME}: ${PWD}\007"'
    ;;
*)
    ;;
esac

It sets that PROMPT_COMMAND if running xterm or rxvt, which should support that escape sequence.

  • Do you know why bash appears to read things character by character, rather than reading in a line at a time? Also, why does bash write "l" and "s" to stdout? If I do a similar strace with cat, there are two differences: it reads input line by line, and while it does echo its input back to stdout, I see the input twice (once when I type, and once when cat echoes it). – extremeaxe5 Nov 11 '19 at 16:02
  • @extremeaxe5, that's basically because Bash (or rather the readline library) handles all the command line processing itself, instead of relying on the rather limited processing done by the terminal. It has to get the input immediately to decide what to do when e.g. a TAB character or ^A (Ctrl-A) or the various special characters is pressed. Also, it turns the terminal's echo off, so that it can decide what to output for each particular input character (again, TAB doesn't usually output a TAB.) cat does none of that. If you it, try running dash, which doesn't do any command line handling. – ilkkachu Nov 11 '19 at 18:28
  • Actually, the reason Bash calls read() to only read one byte at a time is that it can't read past a newline. The newline might cause it to run an external program, which might also read from the same input. (And that program should be able to read any characters after the newline.) If it didn't have to care about that, it could call read() with a larger limit, and with the terminal in raw mode, it would still usually get the input one character at a time. (It would be up to how fast the input characters would arrive and how the process was scheduled.) – ilkkachu Nov 11 '19 at 18:37
  • Your second comment appears to only be true because Bash does command-line handling itself. – extremeaxe5 Nov 12 '19 at 16:52
  • @extremeaxe5, well, yes, I was assuming that, since it's the common case anyway. But, even if the shell relied on the terminal's line editing, the timing could still be an issue. If two lines were sent in quick succession (think pasting data), and the system was loaded enough so the shell wouldn't get immediately scheduled (or worse, the shell was stopped), then a read() with larger buffer might still return both lines in the same call. I don't think there's a guarantee that read() would always return just one line in cooked mode. – ilkkachu Nov 12 '19 at 17:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.