jozxyqk
jozxyqk

Reputation: 17396

Execute process in background, without printing "Done", and get PID

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"?

Upvotes: 13

Views: 5684

Answers (5)

Qi Wang
Qi Wang

Reputation: 31

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

Upvotes: 1

konsolebox
konsolebox

Reputation: 75588

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.

Upvotes: 8

Cyrus
Cyrus

Reputation: 88929

Try this:

pid=$((sleep 5 & echo $!) | sed 1q)

Upvotes: 2

clt60
clt60

Reputation: 63974

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...

Upvotes: 4

anubhava
anubhava

Reputation: 786081

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

Upvotes: 4

Related Questions