9

I've written a shell script for testing an API that copies files and echoes its progress after each one.

There is a two second sleep between each copy, so I would like to add the ability to press any key to pause the script to allow deeper testing. Then press any key to resume.

How can I add this in as few lines as possible?

12

You don't need to add something to your script. The shell allows such a functionality.

  • Start your script in a terminal.
  • While is is running and blocking the terminal use ctrl-z. The terminal is released again and your see a message that the process is stopped. (It is now in the porcess state T, stopped)
  • Now do whatever you want. You can also start other processes/scripts and stop them with ctrl-z.
  • Type jobs in the terminal or list all stopped jobs.
  • To let your script continue, type fg (foreground). It resumes the job back into the foreground process group and the jobs continues running.

See an example:

root@host:~$ sleep 10 # sleep for 10 seconds
^Z
[1]+  Stopped                 sleep 10
root@host:~$ jobs # list all stopped jobs
[1]+  Stopped                 sleep 10
root@host:~$ fg # continue the job
sleep 10
root@host:~$ # job has finished
  • like you said, if I run sleep 10; notify-send hello and press CTRL + Z to stop, notify-send hello get executed. if second command is getting executed how come the first process is stopped ? after that if type fg i cant see anything happening, which is obvious, since second command is already executed – Edward Torvalds Nov 26 '14 at 12:10
  • @edwardtorvalds because the two commands are separate. In a script they whould be in a subshell. Write them in a simple script and execute the script. Then ctrl-z to stop and you will see the second command is not executed until the first has finished. Writing cmd; cmd; cmd; is like writing cmd <newline> cmd <newline> .... Alternatively to a script you can write ( cmd; cmd; cmd; ), it would behave like the script, bacuse of the subshell generated by ( – chaos Nov 26 '14 at 12:20
  • i also tried sleep 10. when i press CTRL + Z after 3 seconds and resumed after few second and noticed that sleep command died in less than 7 seconds. which is opposite to what you said, since command nevers gets stopped it just runs in background. – Edward Torvalds Nov 26 '14 at 12:33
  • @edwardtorvalds I noticed that too... Doesn't make sense. I straced the sleep command and found out that the system call used is nanosleep(). It seems to be a defined behavoir of the nanosleep systemcall. restart_syscall() restarts the interrupted system call with a time argument that is suitably adjusted to account for the time that has already elapsed (including the time where the process was stopped by a signal). Read that manpage: man7.org/linux/man-pages/man2/restart_syscall.2.html – chaos Nov 26 '14 at 13:15
  • Several notes to complete @chaos answer (feel free to include them in): when one does ctrl-Z, the job is suspended ("stopped"), so it is not running at the moment, waiting for you to decide to continue it in the foreground (ex: fg %1) or the background (ex: bg %1). (if jobs gives out only 1 number, ie only 1 suspended process, as in the exemple chaos showed: only [1]+ stopped sleep 10, you can omit the %n part. if there are several background processes (running or stopped), you need to designate the one you want with: %n (ex: fg %2 to have %2 resume in the foreground)) – Olivier Dulac May 31 '17 at 13:16
6

If you want to just pause the script whilst remaining inside the script then you can use read instead of sleep.

You can use

read -t to set a timeout for the read
read -n to read one character(effectively just press any key) to continue script

As you haven't provided any code, below is an example of how it could be used.
If q is pressed then read -n1 prevents the script from continuing until a key is pressed.
When a key is pressed then check is reset and the script continues in the loop as normal.

while [[ true ]]; do
    read -t2 -n1 check
    if [[ $check == "q" ]];then
        echo "pressed"
        read -n1
        check=""
    else
        echo "not pressed"
    fi
echo "Doing Something"
done

You can also add stty -echo to the start of the section and stty echo to the end to prevent typing from messing up the screen output

  • @mikeserv I dont think op cares what is read, they just want to pause and resume the script, what do you mean by save the terminal settings as well, i'm only changing one so that seems a bit overkill. – user78605 Nov 26 '14 at 12:23
  • 1
    @mikeserv ahh right, i was just presuming that all stdin would be coming from the user. – user78605 Nov 27 '14 at 6:17
1

With dd you can reliably read a single byte from a file. With stty you can set a min number of bytes to qualify a terminal read and a time out in tenths of a second. Combine those two and you can do without sleep entirely, I think, and just let the terminal's read timeout do the work for you:

s=$(stty -g </dev/tty)
(while stty raw -echo isig time 20 min 0;test -z "$(
dd bs=1 count=1 2>/dev/null; stty "$s")" || (exec sh)
do echo "$SECONDS:" do your stuff here maybe                             
   echo  no sleep necessary, I think                                                          
   [ "$((i+=1))" -gt 10 ] && exit                                                             
done       
) </dev/tty

That is a little example while loop that I mocked-up for you to try out. Every two seconds dd times out on its attempted read of stdin - redirected from /dev/tty - and the while loop loops. That or dd doesn't time-out because you press a key - in which case an interactive shell is invoked.

Here is a test run - the numbers printed at the head of each line is the value of the shell variable $SECONDS:

273315: do your stuff here maybe
no sleep necessary, I think
273317: do your stuff here maybe
no sleep necessary, I think
273319: do your stuff here maybe
no sleep necessary, I think
273321: do your stuff here maybe
no sleep necessary, I think
sh-4.3$ : if you press a key you get an interactive shell
sh-4.3$ : this example loop quits after ten iterations
sh-4.3$ : or if this shell exits with a non-zero exit status
sh-4.3$ : and speaking of which, to do so you just...
sh-4.3$ exit
exit
273385: do your stuff here maybe
no sleep necessary, I think
273387: do your stuff here maybe
no sleep necessary, I think
273389: do your stuff here maybe
no sleep necessary, I think
273391: do your stuff here maybe
no sleep necessary, I think
273393: do your stuff here maybe
no sleep necessary, I think
273395: do your stuff here maybe
no sleep necessary, I think
273397: do your stuff here maybe
no sleep necessary, I think
  • Shouldn't you use stty sane after changing stty setting, I may be wrong but it doesn't look like you reset them anywhere ? – user78605 Nov 26 '14 at 12:30
  • @Jidder - no I save the state of the terminal at the top of the script with s=$(stty -g </dev/tty). Immediately after calling dd I then restore it with stty "$s". The terminal state doesn't care about subshells and so those settings stick regardless of the parent shell. stty sane is not necessarily what you wanna do - it is better to restore the state to the way you found it than to assume the state was sane at that point. If I wasn't restoring it those echos would be all over the place. Figuring that out is partly why I came so late - your answer wasnt here when I started testing. – mikeserv Nov 26 '14 at 15:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.