11

This seems like a pretty trivial thing to do, but I'm very stuck.

To execute something in the background, use &:

>>> sleep 5 &
[1] 21763
>>> #hit enter
[1]+  Done                    sleep 5

But having a bashrc-sourced background script output job information is pretty frustrating, so you can do this to fix it:

>>> (sleep 5 &)

OK, so now I want to get the PID of sleep for wait or kill. Unfortunately its running in a subshell so the typical $! method doesn't work:

>>> echo $!
21763
>>> (sleep 5 &)
>>> echo $!
21763 #hasn't changed

So I thought, maybe I could get the subshell to print its PID in this way:

>>> sleep 5 & echo $!
[1] 21803 #annoying job-start message (stderr)
21803 #from the echo

But now when I throw that in the subshell no matter how I try to capture stdout of the subshell, it appears to block until sleep has finished.

>>> pid=$(sleep 5 & echo $!)

How can I run something in the background, get its PID and stop it from printing job information and "Done"?

Community
  • 1
  • 1
jozxyqk
  • 16,424
  • 12
  • 91
  • 180
  • 3
    FYI, job control is turned off by default in scripts, so you shouldn't see the job number information printed in that scenario. – Charles Duffy Jul 19 '14 at 18:45
  • @CharlesDuffy As jm666 has correctly guessed, I'm sourcing this script and want to have it running in the background of my main shell. Will clarify in the question – jozxyqk Jul 19 '14 at 19:33

5 Answers5

7

Solution A

When summoning the process, redirect the shell's stderr to >/dev/null for that summoning instance. We can do this by duplicating fd 2 so we could still use the duplicate fd for the process. We do all of these inside a block to make redirection temporary:

{ sleep 5 2>&3 & pid=$!; } 3>&2 2>/dev/null

Now to prevent the "Done" message from being shown later, we exclude the process from the job table and this is done with the disown command:

{ sleep 5 2>&3 & disown; pid=$!; } 3>&2 2>/dev/null

It's not necessary if job control is not enabled. Job control can be disabled with set +m or shopt -u -o monitor.

Solution B

We can also use command substitution to summon the process. The only problem we had is that the process still hooks itself to the pipe created by $() that reads stdout but we can fix this by duplicating original stdout before it then using that file descriptor for the process:

{ pid=$( sleep 200s >&3 & echo $! ); } 3>&1

It may not be necessary if we redirect the process' output somewhere like /dev/null:

pid=$( sleep 200s >/dev/null & echo $! )

Similarly with process substitution:

{ read pid < <(sleep 200s >&3 & echo $!); } 3>&1

Some may say that redirection is not necessary for process substitution but the problem is that the process that may be accessing its stdout would die quickly. For example:

$ function x { for A in {1..100}; do echo "$A"; sleep 1s; done }
$ read pid < <(x & echo $!)
$ kill -s 0 "$pid" &>/dev/null && echo "Process active." || echo "Process died."
Process died.
$ read pid < <(x > /dev/null & echo $!)
$ kill -s 0 "$pid" &>/dev/null && echo "Process active." || echo "Process died."
Process active.
  • Optionally you can just create a permanent duplicate fd with exec 3>&1 so you can just have pid=$( sleep 200s >&3 & echo $! ) on the next lines.
Community
  • 1
  • 1
konsolebox
  • 72,135
  • 12
  • 99
  • 105
  • good point about preserving output. I particularly like this as the process doesn't need to run in a subshell too. however, as @jm666 points out I lose job control `-bash: fg: no job control`. for the moment I still prefer the subshell method – jozxyqk Jul 20 '14 at 04:49
4

You can use read bulletin to capture output:

read -r pid < <(sleep 10 & echo $!)

Then:

ps -p $pid
  PID TTY           TIME CMD
78541 ttys001    0:00.00 sleep 10
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • very interesting. could you explain why I need to use `read`? Also, I should have realized this would happen earlier: `bash: wait: pid 22108 is not a child of this shell`. – jozxyqk Jul 19 '14 at 18:49
  • `wait` won't work because sleep is executed in a sub shell and background process id is not sub process of current shell process. – anubhava Jul 19 '14 at 18:53
  • 2
    @jozxyqk, `read` reads a single line and immediately returns without blocking further, whereas typical command substitution continues to try to read until the subprocess's stdout is closed. – Charles Duffy Jul 20 '14 at 02:01
3

The set +m disable monitor mode in bash. In other words it rid off the annnoying Done message. To enable again, use set -m.

eg:

$ set +m
$ (sleep 5; echo some) &
[1] 23545 #still prints the job number

        #after 5 secs
some
$  #no Done message...
clt60
  • 62,119
  • 17
  • 107
  • 194
  • 1
    well this just removes all the complexity doesn't it. cheers! – jozxyqk Jul 19 '14 at 18:53
  • 1
    @jozxyqk not really because it disables job control, so you didn;t get notifications about when background job waiting for input, can't list jobs and so on... Not recommented to use it in `.bashrc` only occasionally when you really know what you doing. ;) – clt60 Jul 19 '14 at 18:57
  • @jozxyqk :) ;) sure. The set is powerful. See the man here: https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html – clt60 Jul 19 '14 at 19:34
2

Try this:

pid=$((sleep 5 & echo $!) | sed 1q)
Cyrus
  • 84,225
  • 14
  • 89
  • 153
  • indeed, this works. can you explain why `sed 1q` behaves differently to say `cat`? – jozxyqk Jul 19 '14 at 19:30
  • `sed 1q` read the first line and then exits immediately. If a member in a pipe terminates (here `sed`), then all others (here `$(...)`) are also terminated and only then can the output assigned to $pid. Only `sleep 5` continues in background. – Cyrus Jul 19 '14 at 20:00
  • If this kind of solution (which blocks stdout) is accepted, then you wouldn't need sed anymore. [See my solution.](http://stackoverflow.com/a/24844613/445221) – konsolebox Jul 19 '14 at 20:21
  • 1
    Does this have any advantage over @anubhava's approach? Strikes me as a less efficient way of doing the same thing (on account of the overhead of using `sed`, vs the in-process builtin `read`). – Charles Duffy Jul 20 '14 at 01:59
  • @CharlesDuffy One obvious thing is that it doesn't require named pipes when necessary. – konsolebox Jul 20 '14 at 05:56
1

I found a great way, no need sub-shell, will keep the parent-child relationship. Since: [1] 21763 and [1]+ Done sleep 5 are all stderr, which is &2. We can redirect &2 to /dev/null, here is code:

exec 7>&2 2>/dev/null # Here backup 2 to 7, and redirect 2 to /dev/null
sleep 5
wait
exec 2>&7 7>&- # here restore 7 to 2, and delete 7.

See: Using exec

Stephen Rauch
  • 47,830
  • 31
  • 106
  • 135
Qi Wang
  • 31
  • 2