6

I am currently starting a background process within a subshell and was wondering how can I assign its pid number to a variable outside the subshell scope?
I have tried many different ways but MYPID always stays set to 0.

MYPID = 0;
({ sleep 2 & }; $MYPID=$!)
({ sleep 2 & }; echo $!) > $MYPID

The only way it returns a value is with:

$MYPID=$({ sleep 2 & }; echo $!)

However this discards the background process instruction and it will only return a result after 2 seconds.

2
  • 2
    What exactly are you trying to achieve? Why not simply sleep 2 & mypid=$!? Commented Sep 24, 2014 at 13:56
  • I am trying to suppress the process creation stdout. Both start showing the PID address and final with Done confirmation.
    – zanona
    Commented Sep 24, 2014 at 14:08

2 Answers 2

9

bash shouldn't print the job status when non-interactive.

If that's indeed for an interactive bash, you can do:

{ pid=$(sleep 20 >&3 3>&- & echo "$!"); } 3>&1

We want sleep's stdout to go to where it was before, not the pipe that feeds the $pid variable. So we save the outer stdout in the file descriptor 3 (3>&1) and restore it for sleep inside the command substitution. So pid=$(...) returns as soon as echo terminates because there's nothing left with an open file descriptor to the pipe that feeds $pid.

However note that because it's started from a subshell (here in a command substitution), that sleep will not run in a separate process group. So it's not the same as running sleep 20 & with regards to I/O to the terminal for instance.

Maybe better would be to use a shell that supports spawning disowned background jobs like zsh where you can do:

sleep 20 &! pid=$!

With bash, you can approximate it with:

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

bash outputs the [1] 21578 to stderr. So again, we save stderr before redirecting to /dev/null, and restore it for the sleep command. That way, the [1] 21578 goes to /dev/null but sleep's stderr goes as usual.

If you're going to redirect everything to /dev/null anyway, you can simply do:

{ apt-get update & } > /dev/null 2>&1; pid=$!; disown "$pid"

To redirect only stdout:

{ apt-get-update 2>&3 3>&- & } 3>&2 > /dev/null 2>&1; pid=$!; disown "$pid"
6
  • That looks great Stéphane. Thanks for that. I really liked the way you delegate outputs there. Would you mind explaining in the answer what each part is doing please? As I am learning this would be a valuable asset :) Thanks again
    – zanona
    Commented Sep 24, 2014 at 14:42
  • Sounds great, is it possible however to omit the output (except errors for the command in question) supposing I use apt-get update instead sleep I usually would send that to > /dev/null however since it is sending ’2>&3 the output still showing?
    – zanona
    Commented Sep 24, 2014 at 14:49
  • That’s marvellous Stéphane. thanks so much for the thorough explanation. The only thing is that on the last example with the apt-get update unfortunately when there is an error it won’t be logged since it is simple discarding everything. I was wondering about a way to keep the errors and discard the rest, however, I am not sure if that is possible. But I just noted that the process output [1] 23929 is sent to stderr so I believe this is a tricky thing :) Thanks once again for your help.
    – zanona
    Commented Sep 24, 2014 at 15:24
  • Thanks for the thorough explanations and in particular for the emulation of spawning disowned background jobs. Commented Nov 17, 2014 at 17:06
  • For those of you wondering: 3>&- closes fd 3 in the end (the innermost part of the code). With the preceding 2>&3, it's essentially a move of fd 3 to 2, which could also be written as 2>&3- (see gnu.org/software/bash/manual/bash.html#Moving-File-Descriptors). Commented Feb 10, 2022 at 17:42
2

second line can't work

({ sleep 2 & }; $MYPID=$!)

the ( ... ) fork a subshell, MYPID is set, then subshell exit and it's value is lost.

third line didn't work either

({ sleep 2 & }; echo $!) > $MYPID

you just echo $! to a file named 0 on local dir.

The only answer I can provide is via a temporary file (as var can't be push back)

 ( { sleep 100 & } ; echo $! > u ) > /dev/null &> /dev/null
 MYPID=$(<u)

you can replace u with any file.

1
  • Sounds good Archemar, I am using this process so far. However, I am not sure if there is a correct way to set temporary files? I am not sure if using mktemp or > /tmp/file Also what would be the best procedure after getting the PID? Should I remove the temporary file?
    – zanona
    Commented Sep 24, 2014 at 14:33

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .