How can I wake a sleeping bash script?

Is it possible to wake a process that is paused using the sleep command?


As an example, lets say you have this script:

#!/bin/bash
echo "I am tired"
sleep 8h
echo "I am fresh :)"

After 30 minutes you discover that you need the script to stop, that is, you wished you had written sleep 30m instead.

You do not want to either call kill PID or press Ctrl+C, since then the last command is not executed and you will remain tired.

Is there a way to wake the process from sleep or perhaps use another command that supports wakeup? Solutions to both background and foreground processes are welcome.


Solution 1:

When a Bash script is running a sleep, here's what the pstree might look like:

bash(10102)───sleep(8506)

Both have process IDs (PIDs), even when running as a script. If we wanted to interrupt the sleep, we'd send kill 8506 and the Bash session would resume... The problem is in a scripted environment we don't know the PID of the sleep command and there isn't a human to look at the process tree.

We can get the PID of the Bash session through the $$ magic variable. If we can store that somewhere, we can then target instances of sleep that are running underneath that PID. Here's what I'd put in the script:

# write the current session's PID to file
echo $$ >> myscript.pid

# go to sleep for a long time
sleep 1000

And then we can tell pkill to nuke sleep instances running underneath that PID:

pkill -P $(<myscript.pid) sleep

Again, this is limiting itself to only sleep processes running directly under that one Bash session. As long as the PID was logged correctly, this makes it a lot safer than killall sleep or pkill sleep, which could nuke any sleep process on the system (permissions allowing).

We can prove that theory with the following example where we have three separate bash sessions, two running sleep. Only because we're specifying the PID of the top-left bash session, only its sleep is killed.

enter image description here


An alternative approach is to push sleep into the background, store its PID and then return it to the foreground. In the script:

sleep 1000 &
echo $! > myscript.sleep.pid
fg

And to kill it:

kill $(<myscript.sleep.pid)

Solution 2:

You could write your script to handle ("trap") other signals from kill etc. so you could modify the scripts behaviour as needed. See man bash:

SIGNALS
   When  bash  is  interactive,  in the absence of any traps, it ignores SIGTERM (so that kill 0 does not
   kill an interactive shell), and SIGINT is caught and handled (so that the wait builtin  is  interrupt-
   ible).   In all cases, bash ignores SIGQUIT.  If job control is in effect, bash ignores SIGTTIN, SIGT-
   TOU, and SIGTSTP.

   Non-builtin commands run by bash have signal handlers set to the values inherited by  the  shell  from
   its  parent.   When  job  control is not in effect, asynchronous commands ignore SIGINT and SIGQUIT in
   addition to these inherited handlers.  Commands run as a result of  command  substitution  ignore  the
   keyboard-generated job control signals SIGTTIN, SIGTTOU, and SIGTSTP.

   The shell exits by default upon receipt of a SIGHUP.  Before exiting, an interactive shell resends the
   SIGHUP to all jobs, running or stopped.  Stopped jobs are sent SIGCONT to ensure that they receive the
   SIGHUP.   To  prevent the shell from sending the signal to a particular job, it should be removed from
   the jobs table with the disown builtin (see SHELL BUILTIN COMMANDS below) or  marked  to  not  receive
   SIGHUP using disown -h.

   If  the huponexit shell option has been set with shopt, bash sends a SIGHUP to all jobs when an inter-
   active login shell exits.

   If bash is waiting for a command to complete and receives a signal for which a trap has been set,  the
   trap  will not be executed until the command completes.  When bash is waiting for an asynchronous com-
   mand via the wait builtin, the reception of a signal for which a trap has been set will cause the wait
   builtin  to  return immediately with an exit status greater than 128, immediately after which the trap
   is executed.

Solution 3:

You could just kill sleep which would continue to the next line of the script:

pkill sleep

Note that this would kill any sleep process running in your system, not only in your script.

Solution 4:

I have a sleeping bash script started by cron on boot. The script wakes every minute and sets the laptop display brightness based on sunrise and sunset obtained from the internet. A user configurable transition phase between full bright and full dim requires stepping up and stepping down values by 3, 4, 5 or whatever is calculated every minute.

Oli briefly touched on pstree in his answer but rejected it because it would kill all sleep instances. This can be avoided by narrowing the search using pstree options.

Using pstree -h we see the entire heirarchy:

$ pstree -h
systemd─┬─ModemManager─┬─{gdbus}
        │              └─{gmain}
        ├─NetworkManager─┬─dhclient
        │                ├─dnsmasq
        │                ├─{gdbus}
        │                └─{gmain}
        ├─accounts-daemon─┬─{gdbus}
        │                 └─{gmain}
        ├─acpid
        ├─agetty
        ├─atd
        ├─avahi-daemon───avahi-daemon
        ├─cgmanager
        ├─colord─┬─{gdbus}
        │        └─{gmain}
        ├─cron───cron───sh───display-auto-br───sleep
        ├─cups-browsed─┬─{gdbus}
        │              └─{gmain}
        ├─dbus-daemon
        ├─fwupd─┬─3*[{GUsbEventThread}]
        │       ├─{fwupd}
        │       ├─{gdbus}
        │       └─{gmain}
        ├─gnome-keyring-d─┬─{gdbus}
        │                 ├─{gmain}
        │                 └─{timer}
        ├─irqbalance
        ├─lightdm─┬─Xorg───3*[{Xorg}]
        │         ├─lightdm─┬─upstart─┬─at-spi-bus-laun─┬─dbus-daemon
        │         │         │         │                 ├─{dconf worker}
        │         │         │         │                 ├─{gdbus}
        │         │         │         │                 └─{gmain}
        │         │         │         ├─at-spi2-registr─┬─{gdbus}
        │         │         │         │                 └─{gmain}
        │         │         │         ├─bamfdaemon─┬─{dconf worker}
        │         │         │         │            ├─{gdbus}
        │         │         │         │            └─{gmain}
        │         │         │         ├─chrome─┬─2*[cat]
        │         │         │         │        ├─chrome─┬─chrome─┬─2*[chrome─┬─{Chrome_ChildIOT}]
        │         │         │         │        │        │        │           ├─5*[{CompositorTileW}]]
        │         │         │         │        │        │        │           ├─{Compositor}]
        │         │         │         │        │        │        │           ├─{GpuMemoryThread}]
        │         │         │         │        │        │        │           ├─{MemoryInfra}]
        │         │         │         │        │        │        │           ├─{Renderer::FILE}]
        │         │         │         │        │        │        │           ├─{TaskSchedulerRe}]
        │         │         │         │        │        │        │           └─{TaskSchedulerSe}]
        │         │         │         │        │        │        ├─7*[chrome─┬─{Chrome_ChildIOT}]
        │         │         │         │        │        │        │           ├─5*[{CompositorTileW}]]
        │         │         │         │        │        │        │           ├─{Compositor}]
        │         │         │         │        │        │        │           ├─{GpuMemoryThread}]
        │         │         │         │        │        │        │           ├─{MemoryInfra}]
        │         │         │         │        │        │        │           ├─{Renderer::FILE}]
        │         │         │         │        │        │        │           ├─{ScriptStreamerT}]
        │         │         │         │        │        │        │           ├─{TaskSchedulerRe}]
        │         │         │         │        │        │        │           └─{TaskSchedulerSe}]
        │         │         │         │        │        │        ├─chrome─┬─{Chrome_ChildIOT}
        │         │         │         │        │        │        │        ├─5*[{CompositorTileW}]
        │         │         │         │        │        │        │        ├─{Compositor}
        │         │         │         │        │        │        │        ├─{GpuMemoryThread}
        │         │         │         │        │        │        │        ├─{Media}
        │         │         │         │        │        │        │        ├─{MemoryInfra}
        │         │         │         │        │        │        │        ├─{Renderer::FILE}
        │         │         │         │        │        │        │        ├─{ScriptStreamerT}
        │         │         │         │        │        │        │        ├─{TaskSchedulerRe}
        │         │         │         │        │        │        │        └─{TaskSchedulerSe}
        │         │         │         │        │        │        └─2*[chrome─┬─{Chrome_ChildIOT}]
        │         │         │         │        │        │                    ├─5*[{CompositorTileW}]]
        │         │         │         │        │        │                    ├─{Compositor}]
        │         │         │         │        │        │                    ├─{GpuMemoryThread}]
        │         │         │         │        │        │                    ├─{Renderer::FILE}]
        │         │         │         │        │        │                    ├─{ScriptStreamerT}]
        │         │         │         │        │        │                    ├─{TaskSchedulerRe}]
        │         │         │         │        │        │                    └─{TaskSchedulerSe}]
        │         │         │         │        │        └─nacl_helper
        │         │         │         │        ├─chrome─┬─chrome
        │         │         │         │        │        ├─{Chrome_ChildIOT}
        │         │         │         │        │        ├─{MemoryInfra}
        │         │         │         │        │        ├─{TaskSchedulerSe}
        │         │         │         │        │        └─{Watchdog}
        │         │         │         │        ├─{AudioThread}
        │         │         │         │        ├─{BrowserWatchdog}
        │         │         │         │        ├─{Chrome_CacheThr}
        │         │         │         │        ├─{Chrome_DBThread}
        │         │         │         │        ├─{Chrome_FileThre}
        │         │         │         │        ├─{Chrome_FileUser}
        │         │         │         │        ├─{Chrome_HistoryT}
        │         │         │         │        ├─{Chrome_IOThread}
        │         │         │         │        ├─{Chrome_ProcessL}
        │         │         │         │        ├─{Chrome_SyncThre}
        │         │         │         │        ├─{CompositorTileW}
        │         │         │         │        ├─{CrShutdownDetec}
        │         │         │         │        ├─{D-Bus thread}
        │         │         │         │        ├─{Geolocation}
        │         │         │         │        ├─{IndexedDB}
        │         │         │         │        ├─{LevelDBEnv}
        │         │         │         │        ├─{MemoryInfra}
        │         │         │         │        ├─{NetworkChangeNo}
        │         │         │         │        ├─{Networking Priv}
        │         │         │         │        ├─4*[{TaskSchedulerBa}]
        │         │         │         │        ├─6*[{TaskSchedulerFo}]
        │         │         │         │        ├─{TaskSchedulerSe}
        │         │         │         │        ├─{WorkerPool/3166}
        │         │         │         │        ├─{WorkerPool/5824}
        │         │         │         │        ├─{WorkerPool/5898}
        │         │         │         │        ├─{WorkerPool/6601}
        │         │         │         │        ├─{WorkerPool/6603}
        │         │         │         │        ├─{WorkerPool/7313}
        │         │         │         │        ├─{chrome}
        │         │         │         │        ├─{dconf worker}
        │         │         │         │        ├─{extension_crash}
        │         │         │         │        ├─{gdbus}
        │         │         │         │        ├─{gmain}
        │         │         │         │        ├─{gpu-process_cra}
        │         │         │         │        ├─{inotify_reader}
        │         │         │         │        ├─{renderer_crash_}
        │         │         │         │        ├─{sandbox_ipc_thr}
        │         │         │         │        └─{threaded-ml}
        │         │         │         ├─compiz─┬─{dconf worker}
        │         │         │         │        ├─{gdbus}
        │         │         │         │        ├─{gmain}
        │         │         │         │        └─8*[{pool}]
        │         │         │         ├─conky───6*[{conky}]
        │         │         │         ├─2*[dbus-daemon]

( .... many lines deleted to fit in 30k limit .... )

        ├─vnstatd
        ├─whoopsie─┬─{gdbus}
        │          └─{gmain}
        └─wpa_supplicant

As you can see a typical Ubuntu login contains many many PIDs (Process ID's).

We can narrow it down to our running script using:

$ pstree -g -p | grep display-auto
  |-cron(1198,1198)---cron(1257,1198)---sh(1308,1308)---display-auto-br(1321,1308)---sleep(26552,1308)

We see:

  • cron started a shell (process ID 1308 and session ID 1308)
  • The shell calls our program running under process ID 1321 and session ID 1308 (matching the shell)
  • Our program calls sleep under process ID 26552 and again session ID 1308

At this point we can use pkill -s 1308 and it would kill the entire session which includes the shell, our program display-auto-brightness and the sleep command. Instead we will use kill 26552 to only kill the sleep command forcing our program to wake up and adjust the brightness.

Typing this manually in the terminal you see:

───────────────────────────────────────────────────────────────────────────────
rick@dell:~$ pstree -g -p | grep display-auto
             |-cron(1198,1198)---cron(1257,1198)---sh(1308,1308)---display-auto-br(1321,1308)---sleep(32362,1308)
───────────────────────────────────────────────────────────────────────────────
rick@dell:~$ sudo kill 32362
───────────────────────────────────────────────────────────────────────────────
rick@dell:~$ pstree -g -p | grep display-auto
             |-cron(1198,1198)---cron(1257,1198)---sh(1308,1308)---display-auto-br(1321,1308)---sleep(1279,1308)
───────────────────────────────────────────────────────────────────────────────
rick@dell:~$ sudo kill 1279
───────────────────────────────────────────────────────────────────────────────
rick@dell:~$ pstree -g -p | grep display-auto
             |-cron(1198,1198)---cron(1257,1198)---sh(1308,1308)---display-auto-br(1321,1308)---sleep(4440,1308)
───────────────────────────────────────────────────────────────────────────────
rick@dell:~$ 

The next step is to do it when the laptop wakes up from suspend. For example when lid closed it was full dark and screen brightness was set at "300". When lid is opened it is daylight and brightness needs to be set to "2000". Of course the program would wake up on it's own in 1 to 59 seconds but it's more comfortable for the brightness to be set instantly.

I'll post the suspend/resume code after it's written. Hopefully this weekend.