0

In the below one-liner, I run an "infinite" while loop which prints some numbers:

$ bash -c 'trap stopit SIGINT; run=1; stopit() { run=0; }; while [ $run ]; do for i in {0..4}; do v=$(($i*50)); d=$(for ((k=0;k<=(5+$i);k++)); do echo -n $(($v*(($k+$i)%2))),; done); d=${d%?}; c=$(echo numbers $d); echo $c; sleep 0.1; done; done ; echo Done'
numbers 0,0,0,0,0,0
numbers 50,0,50,0,50,0,50
numbers 0,100,0,100,0,100,0,100
numbers 150,0,150,0,150,0,150,0,150
numbers 0,200,0,200,0,200,0,200,0,200
numbers 0,0,0,0,0,0
numbers 50,0,50,0,50,0,50
numbers 0,100,0,100,0,100,0,100
...

... and the "expanded" script is:

trap stopit SIGINT; 
run=1; 
stopit() { 
  run=0; 
}; 
while [ $run ]; do 
  for i in {0..4}; do 
    v=$(($i*50)); 
    d=$(for ((k=0;k<=(5+$i);k++)); do echo -n $(($v*(($k+$i)%2))),; done); 
    d=${d%?}; # cut final comma
    c=$(echo numbers $d); 
    echo $c; 
    sleep 0.1; 
  done; 
done ; 
echo Done

The idea is that the while loop runs "forever" and prints (running task), and once you get bored, you stop it by pressing Ctrl-C. However, what I want, is to print a message after Ctrl-C has interrupted the while loop - in the above example, that is the echo Done command.

In the above example, I hoped that Ctrl-C would set the run variable to 0, thereby making the loop exit "cleanly" which would then print the command and exit. Unfortunately, when I press Ctrl-C nothing happens, that is, loop keeps on going and then I have to explcitly kill it.

How can I make the above script/one-liner exit the while loop on Ctrl-C, and print the final message?

sdbbs
  • 480
  • 1
    In while [ $run ], without any tests inside the [ ... ] (only one argument other than the closing ]), all you're checking is if the provided string is empty or not. Both 0 and 1 are not empty, so that test will always be true. – muru Mar 29 '23 at 06:03
  • Thanks, @muru - did not even think about quoting, great catch; but probably even quoting would not have helped, due to local scope of variables in bash functions. Turns out, best is just to have the trap handler print final message, and then just exit - posted my example below. – sdbbs Mar 29 '23 at 06:08
  • 1
    No, quoting wouldn't have helped. What might have is comparing the value: [ "$run" = 1 ], so: bash -c 'trap stopit SIGINT; run=1; stopit() { run=0; }; while [ $run = 1 ]; do for i in {0..4}; do v=$(($i*50)); d=$(for ((k=0;k<=(5+$i);k++)); do echo -n $(($v*(($k+$i)%2))),; done); d=${d%?}; c=$(echo numbers $d); echo $c; sleep 0.1; done; done ; echo Done' – muru Mar 29 '23 at 06:11
  • 1
    Variables are locally scoped in functions only if you use local or declare without -g, IIRC, so that's probably unrelated. – muru Mar 29 '23 at 06:14

1 Answers1

0

Ok, got it, I think:

$ bash -c 'trap stopit SIGINT; stopit() { echo Done; exit; }; while [ 1 ]; do for i in {0..4}; do v=$(($i*50)); d=$(for ((k=0;k<=(5+$i);k++)); do echo -n $(($v*(($k+$i)%2))),; done); d=${d%?}; c=$(echo numbers $d); echo $c; sleep 0.1; done; done ;'
numbers 0,0,0,0,0,0
numbers 50,0,50,0,50,0,50
numbers 0,100,0,100,0,100,0,100
numbers 150,0,150,0,150,0,150,0,150
numbers 0,200,0,200,0,200,0,200,0,200
numbers 0,0,0,0,0,0
numbers 50,0,50,0,50,0,50
numbers 0,100,0,100,0,100,0,100
numbers 150,0,150,0,150,0,150,0,150
numbers 0,200,0,200,0,200,0,200,0,200
numbers 0,0,0,0,0,0
Done

Basically, the concept with "changing a global variable to exit cleanly" will not work because variables in functions in bash are always local scope, and "shadow" the globals, see:

So after reading

... it turns out, its best to move the final command ("echo Done") in the trap handler, and after it, from the trap handler, issue exit directly (don't bother with the "exit the while loop cleanly to print afterwards" thing)

sdbbs
  • 480