You can adapt your method a bit. Instead of piping to
cowsay directly, read output till a delimiting character, send that output to
cowsay, then print that character after every command:
exec > >(while IFS= read -d '' -r line; do if [[ -n $line ]]; then echo; printf "%s\n" "$line" | cowsay; fi; done)
Here, I'm using the ASCII NUL character. You can use something else that's unlikely to appear in command output.
This will print after the prompt, so the output will be ugly:
$ export LC_ALL=C
$ exec > >(while IFS= read -d '' -r line; do if [[ -n $line ]]; then echo; printf "%s\n" "$line" | cowsay; fi; done)
$ PROMPT_COMMAND='printf "\0"'
/ Desktop Documents Downloads Music \
| Pictures Public Templates Videos
\ examples.desktop /
$ echo foo
< foo >
Note that this will break any command which tries complex output or has a text user interface (think command line editors, pagers, etc.).
Assuming you already know what
exec > >(...) does, the part in the process substitution is:
while IFS= read -d '' -r line; do ... done: this is a fairly common idiom for reading data delimited by the ASCII NUL character:
IFS= sets the IFS to the empty string, which disables field splitting
read from treating
\ in the input specially (so,
\n for example, is read as
\n and not converted to the newline character).
-d '' is the way to tell
read to read until the NUL character
So the whole thing loops over input in NUL-delimited sections, while preserving the contents of the input as much as possible.
if [[ -n $line ]]; then ... fi; done - only act if the input read so far is not empty.
echo; printf "%s\n" "$line" | cowsay; - print a leading empty line, so that the cowsay output doesn't clash with the prompt, and then send the input read so far to cowsay.
printf is more reliable and safer than