5

From the previous question it seems I failed to understand how && and ^Z interact properly:

$ command1 && command2 && command3
Running command1 ...
Running command2 ...^Z
[1]+  Stopped
$ fg && command4
Running command3
Running command4

, which I thought can be used to "append" the chain after it started, seemed to work only because of command3 was the last command; and stopping it on command2, for example, whould have provided different result (command3 whould have been ignored).

How do I suspend the && chain gracefully, without cancelling the rest of the commands? I want also the exit code to be preserved if it is needed for further logic in the command line.

If I know that I may want to suspend the chain in advance, I can start it like that: ( command1 && command2 && command3 ), so it whould be gracefully suspendable. Buw how do I do it when it is started plainly?

If the commands don't provide output (unlike in example above), should not be run twice or out of order then the only way I can think up is pressing ^Z, analysing what was stopped, then manually constructing fd && the_rest_of_commands, which is inconvenient and error-prone.

3 Answers 3

2

I think you're misinterpreting what's happening. When you do:

cmd1 && cmd2

The shell starts cmd1 in a child process, waits for that process, and then starts cmd2 if cmd1 exited with a zero exit status.

If you press Ctrl+Z, the wait returns, the shell sets $? to 148 for instance (128 + SIGTSTP), and the evaluation of the rest is done at that point, so cmd2 is not executed. And cmd1 becomes a background job.

Had you typed:

cmd1 || cmd2

Or:

cmd1; cmd2

cmd2 would have been executed upon Ctrl-Z and you'd have had to wait for it to finish to get the next prompt (where you can fg or bg your background job).

In zsh, when you press Ctrl-Z in:

{cmd1 && cmd2 && cmd3}

That suspends the whole group (and moves it to a subshell). However, upon resume, the suspended command, when it terminates will set $? to 20 regardless of its exit status and I don't think there's a way around that, so you end up with the same behavior (though in {cmd1 || cmd2} cmd2 would be executed after cmd1 has finished instead of straight on the Ctrl-Z).

5
  • Had you typed doesn't suid. I could have typed (cmd1 && cmd2) if I cared in advance. Also I don't want other commands to be executed in case of failure.
    – Vi.
    Aug 21, 2014 at 13:32
  • When I press Ctrl+Z I usually don't want some action to be automatically started...
    – Vi.
    Aug 21, 2014 at 18:33
  • @Vi. I agree the behavior is less than ideal, but it's the way it is. In that regard, the (t)csh behavior (where job control comes from) is better IMO: CTRL-Z discards the other actions on the command line. Aug 21, 2014 at 18:54
  • Why does ksh behave differently (sleep 1 && sleep 2 executes sleep 2 if you press Ctrl+Z during sleep 1)? What does POSIX have to say? Aug 21, 2014 at 22:37
  • @Gilles, sleep is builtin in ksh93 and mksh. Use /bin/sleep for testing. Aug 22, 2014 at 6:18
1

Looks like suspending the currently running component with gdb -p provices graceful pause, unlike kill -STOP.

So if you know that command2 is currently being executed, you can do:

gdb -p $(pgrep -f 'command2')

from other terminal to suspend, then exit or continue GDB to resume.

0

You can suspend the parent shell - the interactive shell you where starting the commands in:

Print the PID before you run the commands, (or find the pid later, see below):

$ echo $$
1234

From another terminal, you can stop the whole command list by suspending the interactive shell - the list consists of child processes of the interactive shell:

$ kill -STOP 1234

And continue the command list with:

$ kill -CONT 1234


As it's an important part of your use case that there are no preparations (like echo $$) required before running the command list, here is how you get the PID of the shell you want to stop when the list is already running in foreground.

The idea is to find the PID of the parent process of any of the commands in the list:

First, we use pgrep to find the PID of the currently running command. Finding the PID of any of them is enough, as only one runs at a time:

$ pgrep 'command1|command2|command3'

If command1 etc are not really the command name of the process - that is, the first word of the command line - you need to add the -f option (and use -a for experimenting to see more than just the PID):

$ pgrep -f 'command1|command2|command3'

Then, we use ps to find the parent PID of the command:

$ ps -o ppid= 

Combined, we get the PID of the shell to suspend like this:

$ pid=$(ps -o ppid= $(pgrep 'command1|command2|command3'))

Alltogether:

$ kill -STOP $(ps -o ppid= $(pgrep 'command1|command2|command3'))
$ kill -CONT $(ps -o ppid= $(pgrep 'command1|command2|command3'))
6
  • 1
    Why by kill -STOP 1234 the currently executing command gets stopped? Shouldn't it only stop the shell, continuing the command (and stop&waiting only before the next command need to be executed)?
    – Vi.
    Aug 21, 2014 at 13:38
  • BTW I found out that gdb -p PID suspends the process more gracefully rather than kill -CONT...
    – Vi.
    Aug 21, 2014 at 13:39
  • Regarding why stopping the parent stops the children too: I think that kill, or the shell, will send the signal to the whole process group, as it happens in other cases. At least it worked with a simple test. I'll try to find a reference. (Btw, in which way is gdb -p PID more graceful?) Aug 21, 2014 at 15:10
  • @Vi. Yes, that's exactly right as you describe: the kill -STOP 1234 stops only the shell. When using ^Z in the shell, the process group gets signaled. But not with kill -STOP... Hmm... need to do some cleanup... Aug 21, 2014 at 16:50
  • I though about kill -STOP -1234 (for the whole process group), but can it starle the shell (when resumed again), so it cancels the rest?
    – Vi.
    Aug 21, 2014 at 18:34

You must log in to answer this question.

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