16

What I am trying to do is write a Bash script that sleeps for a set amount of time before using the mac say command to speak some text.

I'd like to be able to run the command and then close the terminal so it will still speak at the set time. I've looked into nohup, detach, launchd, and putting the process in the background, but all of these solutions still result in the process being terminated once the terminal is closed. Should I somehow make some sort of zombie child process to do this? What is the best solution? Thank you

# Simple Example of main code
sleep 10;
say hello;
exit;
mit
  • 10,441
  • 7
  • 43
  • 71
Warpling
  • 1,747
  • 2
  • 19
  • 32

8 Answers8

13

Section 3.7.6 of the Bash Manual says:

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 SIGHUP signal to a particular job, it should be removed from the jobs table with the disown builtin (see Section 7.2 [Job Control Builtins], page 88) or marked to not receive SIGHUP using disown -h.

So, using either nohup or disown should do the trick. Or you can do:

trap "" 1
sleep 10
say hello

That 'trap' line ignores signal 1, SIGHUP; you can probably also write 'trap "" HUP".

Jonathan Leffler
  • 666,971
  • 126
  • 813
  • 1,185
  • Thank you, I wasn't aware of SIGHUP, but that makes sense. This works quite well for what I am trying to do. :) – Warpling Dec 06 '10 at 19:25
12
nohup yourscript.sh 10 "hello" &

#     ^your script  ^^your parameter 1
#                      ^^^^^^^your parameter 2

This will detach the script from the terminal, and it won't be killed when the terminal closes. Note the & at the end; you can pass parameters to your script normally. Then yourscript.sh could be:

#!/bin/bash

sleep $1;
say "$2";
exit;
Piskvor left the building
  • 87,797
  • 43
  • 170
  • 220
  • I neglected to mention that in my script the time and message are variable. If I run the command like this I never see my prompts nor can I input variables. :/ – Warpling Dec 06 '10 at 19:19
6

You need to use nohup and background together. I just tried this on OS-X to verify that it works:

nohup ./say-hello.sh &
Laurence Gonsalves
  • 125,464
  • 31
  • 220
  • 273
  • I had neglected to mention that in my script the time and message are variable. If I run the command like this I never see my prompts nor can I input variables. :/ – Warpling Dec 06 '10 at 19:20
5

If you do not start it with nohup, as already suggested, you would need to use disown as such ...

$ ./say-hello.sh &
[1] 12345
$ disown -h %1

You will need to make note of the job number (the second line in the example above, the job number is in the brackets and the other is the process id) so that you can pass it to disown.

Will
  • 2,940
  • 4
  • 25
  • 38
  • I am trying to make the script as self contained as possible so someone without bash or even terminal experience could run it. I suppose if I put this in another script though this would be feasible. Thank you! – Warpling Dec 06 '10 at 19:27
2

Your script could look like this:

#!/bin/bash
read -p "Text: " text
read -p "Delay: " delay
nohup bash -c "sleep $delay; say \"$text\" &"

Then you would run your script normally:

$ your_script
Text: hello
Delay: 10

and the outer script would exit, but the Sleep&Say™ would be left running in the background.

Dennis Williamson
  • 303,596
  • 86
  • 357
  • 418
1

All the solution here are very good, but they don't provide an easy way to "look" what happen in the program without doing a redirection that would fill the hard drive.

Using screen you can then very easily run a script, close your terminal/ssh session, and then come back latter, and "attach" again to the script. Do do that it's pretty easy.

Install

First install screen:

sudo apt-get install screen

Detach

and then put in your bash file

#!/usr/bin/env bash
screen -S myscreen -d -m bash -c 'ls; exec bash'

(replace ls with your program) It will create (-S) a "screen" named myscreen, and detach it (-d) by running the commands inside the ``-c``` option. Then if you want to connect later to this screen:

And attach later

screen -rd myscreen

if you want to list all screen currently running:

screen -ls

NB: if you want to close the screen when it's finished, remove the bash at the end of the command.

tobiasBora
  • 1,052
  • 8
  • 16
0

Use nohup yourscript.sh & as Piskvor suggested.

However, note that you won't be able to regain "control" of your process. Therefore, if you do need it, I suggest adding logging to a file so you know what your program is doing.

Killing your program might not be possible without kill -9, and that might be a bit brutal. If you don't want that, I'd suggest pooling for a file like end.txt every minute or so. If your program detects presence of such a file in it's working directory, it should exit gracefully.

darioo
  • 43,550
  • 10
  • 71
  • 102
  • What about `kill -15` - SIGTERM (can be caught & handled, and is intended for graceful termination) as opposed to `kill -9` - SIGKILL (cannot be caught or ignored)? – Piskvor left the building Dec 08 '10 at 08:51
  • @Piskvor: that's also possible, but you have to implement that behaviour. If that isn't a problem, it would be desirable. – darioo Dec 08 '10 at 08:56
0

Depending on your definition of 'easy' the answer may be daemon.

sorpigal
  • 23,262
  • 7
  • 54
  • 73