9

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.

5
  • It's not possible if the program is running directly in the terminal, because there's only one cursor position and zsh and the program would be competing for it. You could do something with zpty: have the command run in a terminal created by zsh, with its output relayed to the real terminal under zsh's control. May 31, 2011 at 21:13
  • @Gilles Could you elaborate on the usage of zpty ? I am aware that you can't have two programs writing to the same terminal without some kind of competition over the cursor. My question comes from the fact that zsh already rewrites the prompt in some configurations. Obviously this program would have to write to some temporary in-memory buffer that would then be "printed" to the specific location once the command finishes. All of the latter under control of zsh.
    – unode
    Jun 1, 2011 at 16:29
  • @Gilles: maybe this is worth a real answer? Feb 15, 2012 at 19:13
  • @StéphaneGimenez Sure, it is. I don't know how to do it without some research, and I don't have time for this research right now. Feb 15, 2012 at 19:48
  • An idea would be to do this with screen with a startup script that splits the screen and runs the long runner in the upper part and the normal command line in the lower part.
    – vasquez
    Feb 16, 2012 at 15:08

1 Answer 1

5
+100

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!

8
  • This works. But how hard would it be to prevent the shell from printing the process pid when sent to background and the "done" message upon completion? (I mean temporarily deactivate that feature). Also, before putting the output on the terminal, how hard would it be to clear the existing prompt line or lines?
    – unode
    Feb 16, 2012 at 19:22
  • I think I might have some other prompt rewrite config that is colliding with the deletion of the initial prompt. Mine doesn't go away once the command finishes. I also still get the PIDs of the processes when sent to background but the "done" line is gone. In any case I'm accepting your answer as it's pretty close to what I had in mind. Thanks.
    – unode
    Feb 16, 2012 at 22:32
  • I'm using xterm and I have the VCS module enabled and some other customizations to the prompt. I haven't tried but I'm quite sure it's related with that. Some time ago I tried using the vi mode notifier similar to this one (stackoverflow.com/a/3791786/125801) and when active I lost some of my custom prompt behavior. It's not a big deal, at the moment I'm happy with the solution. I'll try to cleanup my prompt one of these days and check if the PIDs still show up.
    – unode
    Feb 17, 2012 at 0:22
  • Zsh-4.3.10 , after disabling pretty much every customization, I now no longer get the prompt (i.e. it's properly removed) but I still get the PIDs of the processes in background, like [1] 27911 27912 in a single line and as the first line on the terminal.
    – unode
    Feb 17, 2012 at 0:51
  • 1
    Just an addition, you can also use &! to do the job asynchronously, In the above example, I believe the monitor lines could be removed and change the call line to: ./testout |& bufferout &! I have it working this way but with buffering to a temp file. Apr 22, 2019 at 11:15

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.