Terminal Zsh – Displaying Stdout of Background Process in Specific Location

terminalzsh

I have a command that I run every time a new terminal is opened or a new login is made.

This program produces output (colored) which should be positioned before the command prompt. It can take a few seconds to run which will prevent me from using the terminal until then (unless ran in background).

Given than zsh has some advanced ways of redrawing the terminal without clobbering existing text, I would like to know how can I run this command in a way that I don't have to wait for it to finish before I can use the terminal but that once it finishes it prints the output as if it was not backgrounded in the first place.

In practice I would like something that could do:

Command output:
... (running on background)
me@computer: somecommand
me@computer: someothercommand

and once the command finishes I would get:

Command output:
 * Output foo
 * Multiple lines bar
 * and some yada
me@computer: somecommand
me@computer: someothercommand

I tried putting the process in background at start but then it does not display the output cleanly. I get something like:

Command output:
[2] 32207
me@computer: somecommand
me@computer: someother * Output foo
 * Multiple lines bar
 * and some yada
[2]  + done       command
me@computer: someothercommand

So, is this possible? If not with zsh is there any solution out there that could do it?

Any pointers or information is welcome.

Best Answer

This is a simple solution if you're willing to accept output just above the current prompt line.

bufferout () {
    local buffer
    while IFS= read -r line; do              # buffer stdin
        buffer="$buffer$line\n"
    done
    print -rn -- $terminfo[dl1]              # delete current line
    printf "$buffer"                         # print buffer
    kill -USR1 $$                            # send USR1 when done
}

TRAPUSR1 () {                                # USR1 signal handler
    zle -I                                   # invalidate prompt
    unhash -f TRAPUSR1 bufferout             # remove ourselves
}

./testout 2>&1 | bufferout &!                # run in background, disowned

When the job is done, the current prompt and input buffer will be removed and the entirety of the command's stdout and stderr will be printed.

Note that this assumes it will be run exactly once and removes itself afterwards. If you want to keep reusing this functionality in the same shell, remove the unhash -f line in TRAPUSR1.


This answer includes an improvement suggested by Clint Priest in the comments. Thanks!