Question

How can I return to my bash prompt automatically after printing output from a function that was put in the background?

For example, when I run the following script in a bash shell:

fn(){
        sleep 10
        echo "Done"
        exit
}
fn &

After running the script, it immediately returns my prompt. After 10 seconds, it prints "Done" and then displays a blinking cursor on a new line:

$ Done
▏

The script isn't running anymore, but I don't get my prompt back until I press Return.

Is there any way to force a return to the bash prompt after printing "Done"?

A related question is: Is there a way for a backgrounded task to inform the terminal to print a new prompt? However, that question asks about a backgrounded program. The answer supplied there applies to a program that is sent to the background, but doesn't seem to work for a function that is sent to the background (as in the example I supplied).

To clarify: I am looking to save the entire code snippet above (e.g., as myscript.sh) and then run it as a foreground script (e.g., as bash myscript.sh).

EDIT: The above is of course just a MWE. The context of this problem is:

  1. User runs script
  2. Script submits PBS job, starts tailing the output file in the background, and calls fn &
  3. User gets prompt back, may start doing other things.
  4. Job output appears on user's terminal when job starts running
  5. fn monitors the queue and kills tail when the job finishes.
  6. Users complain about not getting prompt back (i.e., having to press Enter) after this finishes.

Here's some less minimal code:

watch_queue(){
    until [  `qstat | grep $job | wc -l` -lt 1 ]; do
        sleep 2
    done
    kill -9 $pid
    tput setaf 7
    tput setab 0
    echo "Hit ENTER to return to your command prompt."
    tput sgr0
    exit 0
}

cmd="something complicated that is built at runtime"
outfile="ditto"
queue="selected at runtime, too"

job=`echo "cd \$PBS_O_WORKDIR  && $cmd >> $outfile " | 
     qsub -q $queue -e /dev/null -o /dev/null | 
     awk 'BEGIN { FS="." } { print $1 }'`

echo "Job $job queued on $queue: $cmd"
eval "tail -f -F $outfile 2>/dev/null &"
pid=$!
watch_queue &

Of course it would be a lot easier for me if my users could just pick up the job output from a separate file, or manipulate jobs between foreground and background on their own, but they can't. They can't even follow the instructions in the script to hit Enter to get the "look" of a prompt back... And I can't open another "window" - they do not have a display server.

Was it helpful?

Solution 2

Compile below code to file a.out

#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    /* char buf[] = "date\n"; */
    char buf[] = "\n";  /* Data to write on terminal */
    int i;
    int fd = open(argv[1], O_WRONLY);  /* Open terminal */

    /* printf("fd = %d\n", fd); */
    for (i = 0; i < sizeof buf - 1; i++)  /* Write data */
      ioctl(fd, TIOCSTI, &buf[i]);
    close(fd);  /* Close file descriptor */
    return 0;
}

This program expects a path as command line argument. Program will open the path and write a new line to this path.

If this path happen to contain the file descriptor of a writable terminal running bash script, this would cause bash to catch a new prompt.

Modify your shell script

fn(){
        sleep 10
        echo "Done"
        ./a.out /proc/$PPID/fd/0
}
fn &

This script would do some job (represented with sleep here) and then call the utility written previously with argument as input terminal of parent. Parent terminal would receive a new line and catch a new prompt discarding the stray command on this prompt if any.

/proc contains directories for all processes. Name of folder matches to the pid of process. Inbuild variable PPID contains the parent's pid. Inside the pid directory, there is an fd directory containing open streams. 0 is for input, 1 is for output and 2 is for error. There may be more open streams depending on the process. We are interested in 0 stream here.

OTHER TIPS

What is the problem you're trying to solve?

Right now, this is more or less a cosmetic problem. You're still in the shell, and the prompt is still there. Just type another command and it will be executed.

Alternatively, run the function in the foreground, or if you need to do something else in between, use wait:

$ fn & pid=$!
$ : something else
$ wait ${pid}

similar to Henk Langevelds solution.

Find the pid of your script
wait for it to finish.
echo a line
Prompt is back
Unfortunately you're going to get that blank line still

#!/bin/bash

fn(){
    sleep 1
    echo "Done"

}
fn &
PID=$!
wait $PID
echo -e ''

Rather than just hit ctrl+c (which can accidentally cancel stuff), you can just tell your users to hit return again. That will give you a tidy new prompt.

Bash is really a mess. Developers of bash should not treat the area after the command prompt as a dumping ground for messages. It's friggin horrible UX. lol

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top