6

ctrl-z (^z) acts in ways I do not understand when done inside a loop executed from a terminal.

Say I type

for ii in {0..100}; do echo $ii; sleep 1; done

then I hit ^z. I'll get:

[1]+  Stopped                 sleep 1

I can resume the job using fg or bg, but the job refers only to he sleep command. The rest of the loop has apparently disappeared, and no more number appear on the terminal.

I could use & after the command to immediately run it in the background, or another solution is to wrap the whole thing in a subshell:

( for ii in {0..100}; do echo $ii; sleep 1; done )

then ^z gives me

[1]+  Stopped                 ( for ii in {0..100};
do
    echo $ii; sleep 1;
done )

This job can be resumed and everyone is happy. But I'm not generally in the habit of doing this when running a one-off task, and the question I am asking is why the first behavior happens in the first place. Is there a way to suspend a command-line loop that isn't subshell'd? And what happened to the rest of the loop in the first example?

Note that this is specific to the loop:

echo 1; sleep 5; echo 2

and hitting ^z during the sleep causes the echo 2 to execute:

1
^Z
[2]+  Stopped                 sleep 5
2

Or should I just get in the habit of using & and call it dark magic?

Andrew Schwartz
  • 4,440
  • 3
  • 25
  • 58

1 Answers1

6

You cannot suspend execution of the current shell. When you run your loop from the command line, it is executing in your current login shell/terminal. When you press [ctrl+z] you are telling the shell to suspend the current active process. Your loop is simply a counter in the current shell, the process being executed is sleep. Suspend only operates on sleep.

When you backgroud a process or execute it in a subshell (roughly equivalent), you can suspend that separate process in total.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • 1
    But if suspend only operates on that single sleep command, why don't the rest of the commands execute (as in the echo 1; sleep 5; echo 2 example)? I would expect in that case for the sleep to suspend and the rest of the loop to continue immediately. It seems ^z has not just suspended sleep but also killed the loop, and this is what I don't understand. – Andrew Schwartz Nov 14 '14 at 22:14
  • 1
    Yes, It kills the loop. You have told bash to suspend what it is doing. The only part of that it can suspend is the sleep. The rest goes away. I'll look for a reference for you. Read `man bash (JOB CONTROL)` The reason being, when suspend is invoked, control must return to the shell. There is no way to keep the currently executing loop while returning control to the shell, and since it has no separate pipeline that can be suspended, it terminates to return control to bash. – David C. Rankin Nov 14 '14 at 22:21