24

Practically, I need a process which behaves, as if I had pressed Ctrl+Z just after it started.

Hopefully, it is possible to do such thing using a shell script.

(Also, knowing the resulting PID would be great, so I could continue the process afterwards.)

3 Answers 3

9

After starting a process, you can send it SIGSTOP to suspend it. To resume it, send SIGCONT. I think that this little script may help you

#!/bin/bash
$@ &
PID=$!
kill -STOP $PID
echo $PID
wait $PID

It runs process (command send as parameter), suspends it, prints process id and wait until it ends.

4
  • 1
    I modified it to continue at the end: [enter] #!/bin/bash [enter] $@ & [enter] PID=$! [enter] kill -STOP $PID [enter] echo "Suspended: $PID, press ENTER to continue it" [enter] read [enter] kill -CONT $PID [enter] wait $PID [enter] echo Commented Jul 25, 2011 at 5:35
  • 10
    Just so you know, the subprocess may finish before you even get a chance to send the STOP signal to it.
    – MikeyB
    Commented Jul 25, 2011 at 13:06
  • 22
    Another day at the races. Commented Apr 16, 2015 at 12:11
  • unquoted $@ is a liability here
    – Steven Lu
    Commented Nov 4, 2021 at 4:41
31

From which environment are you creating the process?

If you're doing it from an environment such as C code, you can fork() and then in the child, send a SIGSTOP to yourself before the exec(), preventing further execution.

A more generic solution (probably best) would be to create a stub that does so. So the stub program would:

  • Take arguments consisting of the real program's name and arguments
  • send a SIGSTOP to itself
  • exec() the real program with appropriate arguments

This will ensure that you avoid any sort of race conditions having to do with the new processing getting too far before you can send a signal to it.


An example for shell:

#!/bin/bash
kill -STOP $$
exec "$@"

And using above code:

michael@challenger:~$ ./stopcall.sh ls -al stopcall.sh

[1]+  Stopped                 ./stopcall.sh ls -al stopcall.sh
michael@challenger:~$ jobs -l
[1]+ 23143 Stopped (signal)        ./stopcall.sh ls -al stopcall.sh
michael@challenger:~$ kill -CONT 23143; sleep 1
-rwxr-xr-x 1 michael users 36 2011-07-24 22:48 stopcall.sh
[1]+  Done                    ./stopcall.sh ls -al stopcall.sh
michael@challenger:~$ 

The jobs -l shows the PID. But if you're doing it from a shell you don't need the PID directly. You can just do: kill -CONT %1 (assuming you only have one job).

Doing it with more than one job? Left as an exercise for the reader :)

4
  • I was hoping to do this from a shell script... Commented Jul 24, 2011 at 23:32
  • Great, so since you want to do this from a shell script, use the stub program.
    – MikeyB
    Commented Jul 25, 2011 at 2:46
  • This doesn't stop the process. This just stops the wrapper process. The underlying process didn't even get created.
    – SOFe
    Commented May 12, 2021 at 10:10
  • This is not a valid solution because there is still a race condition: the parent process may try to unsuspend the child process before the child process has suspended itself, resulting in the child remaining suspended indefinitely. You also cannot poll for the suspended state of the child since it may need to suspend again later to await some other process waking it, and unsuspending it early could cause problems in that regard.
    – LB--
    Commented Jul 11 at 2:28
23

MikeyB's answer is correct. From this question on superuser, here's a more concise version:

( kill -SIGSTOP $BASHPID; exec my_command ) &

To understand this, one needs to understand how processes are started on unix: the exec system call replaces the currently running program with a new one, preserving the existing PID. So the way an independent process is created is to first fork, and then exec, replacing the running program in the child process with the desired program.

The ingredients of this shell command are:

  1. The parentheses (...) start a sub-shell: another instance of BASH.
  2. The kill command sends the STOP signal to this process, which puts it into the suspended state.
  3. As soon as you allow the process to continue (by sending it the CONT signal), the exec command causes it to replace itself with your desired program. my_command keeps the original PID of the sub-shell.
  4. You can use the $! variable to get the PID of the process.
8
  • 2
    I like this one, as it's fundamentally correct (just that I had to use -s STOP instead of -SIGSTOP). Still wonder: why it has to be $BASHPID instead of $$? (I may not really understand the difference).
    – Hibou57
    Commented Jul 15, 2014 at 8:36
  • 1
    For $$ vs $BASHPID, I check the latter is the PID of the sub‑shell, while not the former. There is a funny issue: applying the tip in a bash script, most often fails, and I have to do something like this: PID=$!; ps -p $PID; kill -s CONT $PID;… it fails if I remove the ps -p $PID part: the process seems to disappears (killed?) without an error message anywhere. It's OK from a interactive shell, the issue is only from script. That's too much mysterious.
    – Hibou57
    Commented Jul 15, 2014 at 9:55
  • I tried to use this solution for listing file descriptors of my_command. (goo.gl/cNbUE8) but the descriptors are still the same no mather what's my_command.
    – rasty.g
    Commented Nov 14, 2017 at 8:11
  • For options to replace $BASHPID for shells other than bash, see here
    – Jakob
    Commented Aug 14, 2019 at 4:08
  • I wonder: Isn't that code relying on a race condition?: The subshell with PID $BASHPID executes two commands in sequence, the kill and the exec. But if the signal arrived before kill exited, won't the shell be stopped before exec is started? Why not ( kill -SIGSTOP $BASHPID& exec my_command ) &? That would still be a race condition, but with a better likelihood for success IMHO.
    – U. Windl
    Commented Apr 20, 2021 at 12:28

You must log in to answer this question.

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