2013-10-21

How to start a Unix process in the foreground instead

This blog post explains how to run a Unix process in the foreground if it automatically puts itself into the background. TL;DR Run it like this:

$ prog 3>&1 | cat

Let's suppose you have two shell scripts:

$ cat >fore.sh <<'END'
#! /bin/sh
echo foo; sleep 1; echo bar
END
$ chmod +x fore.sh
$ cat >back.sh <<'END'
#! /bin/sh
(echo foo; sleep 1; echo bar) &
END
$ _

When running ./fore.sh you have to wait a second for it to finish, because it runs in the foreground. If you want to run it in the background, run it as: ./fore.sh & . You'll get back your prompt instantly, and you'll get the bar message one second later. The & is a standard Bourne shell feature to start processes in the background.

Doing the other way round is a bit trickier. When running ./back.sh, you get your prompt back almost instantly, because the sleeping happens in the background. There is also a race condition: sometimes you get foo first, and then the prompt, sometimes the other way round, depending on whether the kernel schedules the interactive shells or the back.sh script first. If you want to run a command in the foreground, but you don't want to change it in the implementation, a simple solution is this:

$ ./back.sh | cat
foo
bar
$ ./fore.sh | cat
foo
bar
$ _

So appending | cat works no matter the program wants to run in the foreground or in the background. The reason why it works is that you don't get your prompt back until cat has finished, and | cat waits for an EOF on the program's stdout, and there is no EOF on a pipe until all processes writing to that pipe have closed it, i.e. (most of the time) until the entire program terminates.

The | cat solution above breaks if the program is smart and closes its stdout early. In this case you get the prompt back immediately. For example, below bar is displayed only about a second after the $ in front of it was displayed.

$ cat >smart.sh <<'END'
#! /bin/sh
(exec >/dev/null; echo foo >&2; exec sh -c 'sleep 1; echo bar >&2') &
END
$ ./smart.sh | cat
foo
$ bar
_

To make it work for even such a program (in addition to back.sh and fore.sh), run it like this:

$ ./smart.sh 3>&1 | cat

The reason why this works is that 3>&1 instructs the shell to make a copy of the file descriptor 1 (stdout) as file descriptor 3. (Any number between 3 and 9 would do. For smarter shells numbers larger than 9 also work.) So even when the program explicitly closes (or redirects) its own stdout, it will still have file descriptor 3 open until it exits, thus preventing the EOF cat is waiting for, thus making the shell continue waiting for cat, thus not giving back the prompt. Most programs tend not to bother explicitly closing file descriptors larger than 2. The kernel closes file descriptors with the close-on-exec bit automatically closed upon an exec* call. Fortunately that doesn't apply to our file descriptor 3, because the shell has created it using the dup2 system call, which creates file descriptor 3 with the close-on-exec flag off.

No comments: