Most likely, what you are observing depends on line editing and the mode your shell sets the terminal in.
When you paste the text
(including a newline after "b") to the command line of an interactive Bash session, by default it finds the terminal set in raw mode — as it is when line editing is provided by the GNU Readline library; it means that, among other things, input is sent to the shell character by character (not line by line), the conversion of carriage return characters (
\r, what the terminal sees when Enter is pressed or a newline is pasted to the command line) into line feed characters (
\n) is disabled and typed characters are echoed to the command line by the shell itself (not by the terminal).
The shell then reads the first line, finds it is a complete simple command (
cat), and runs it. While doing that it also disables its own line editing, setting the terminal in cooked mode and enabling the
\n conversion. At that point, though, the input buffer already contains
a\rb\r, which is what is read by
cat and printed on the screen. If you then press Ctrl+D (
cat exits and a new prompt is printed, overwriting the (only) printed line (because it ends in a carriage return).
If, in place of
cat, you invoke a program that reads from standard input in a loop, equivalent to the script
while IFS= read -r foo
# Accumulate the content of foo
it will read nothing until you hit Enter, because there is no
\n character in the input buffer (unless the pasted data exceeds the terminal's input buffer size, see below).
After pasting, the text (
b, in this case) you see on the line that follows the command is not the echo of the pasted content, but the output of
cat. You can easily verify this, for instance, by pasting
od -An -tc
Pasting into a shell run as
strace -f -e read,write bash may also help in understanding what is going on.
If the pasted text meant to be read from standard input is "enough" (on my system, more than 4095 characters; see "Canonical and noncanonical mode" in
man 3 termios), what written above only applies to the first 4095 byte-sized read — what the terminal puts in its input buffer when in raw mode, at most. Subsequent reads happen in cooked mode,
\n conversion takes place and that part of the pasted text is echoed to the command line (and likely intermingles with the command's output).
In principle, this behavior can be changed by disabling Bash's line editing:
$ set +o emacs
$ set +o vi
$ cat # Pasting the above snippet here
$ # ^D makes cat exit and brings the prompt back
Note, though, that lengthy input will still intermingle with the output because the terminal has no way to tell the shell to wait until the paste operation is over before starting its processing, unless "bracketed paste" is enabled (see below).
zsh seems to be a bit simpler. The only pitfall: "bracketed paste" is probably enabled by default. It allows you to paste multi-line commands to the command line, executing them as if they were a script while ensuring that special characters (e.g. tabs, newlines) are properly handled. This also means that, when given
zsh reads the three lines and executes
cat, which waits for new input from the terminal; when
cat exits, the shell tries running
a (which fails, unless such a command exists on your system), then
Disabling bracketed paste allows for the behavior you seem to be looking for (but, compared to
bash with no line editing, by default
zsh does not echo the pasted content, except for the part that exceeds the first 4095 bytes (see the bulleted list above)):
% unset zle_bracketed_paste
% cat # Pasting the above snippet here
a # cat prints these two lines
% # ^D makes cat exit and brings the prompt back