Reputation: 17396
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
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
Reputation: 75588
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
.
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.
exec 3>&1
so you can just have pid=$( sleep 200s >&3 & echo $! )
on the next lines.Upvotes: 8
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
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