Home > Software engineering >  How can I combine the commands leave and say?
How can I combine the commands leave and say?

Time:06-04

Is there a way to make use of the command leave and the command say (MacOS) to be notified by the built in voice system?

I can do echo hello | say on the command line.

leave outputs Time to leave! how can this output be piped to say once it appears?

When I issue this command it's just hanging. (no leave process is created)

# one minute from now
leave  0001
Alarm set for Fri Jun  3 15:55:05 CEST 2022. (pid 37692)
pgrep leave
37692
# triggers: Time to leave!

# one minute from now piped to say
# this one is just hanging there.... (no leave process created)
leave  0001 | say

man leave

LEAVE(1)                  BSD General Commands Manual                 LEAVE(1)

NAME
     leave -- remind you when you have to leave

SYNOPSIS
     leave [[ ]hhmm]

DESCRIPTION
     The leave utility waits until the specified time, then reminds you that you have to leave.  You are reminded 5 minutes and 1 minute before the actual time, at
     the time, and every minute thereafter.  When you log off, leave exits just before it would have printed the next message.

     The following options are available:

     hhmm    The time of day is in the form hhmm where hh is a time in hours (on a 12 or 24 hour clock), and mm are minutes.  All times are converted to a 12 hour
             clock, and assumed to be in the next 12 hours.

             If the time is preceded by ` ', the alarm will go off in hours and minutes from the current time.

     If no argument is given, leave prompts with "When do you have to leave?".  A reply of newline causes leave to exit, otherwise the reply is assumed to be a
     time.  This form is suitable for inclusion in a .login or .profile.

     To get rid of leave you should either log off or use `kill -s KILL' giving its process id.

SEE ALSO
     calendar(1)

HISTORY
     The leave command appeared in 3.0BSD.

BSD                             April 28, 1995                             BSD

Thank you!

CodePudding user response:

It sounds like leave writes one line of output per day. If say is trying to read all of stdin at a go (or otherwise do any kind of a read that is not one-character-at-a-time stopping at the first newline), the buffer will never be full enough for its read to complete in a reasonable time period.

The bash read builtin does these (inefficient) one-character-at-a-time reads, and so is able to get content from a pipeline more appropriately (as long as leave is overriding libc's default buffering behavior, which switches from line-buffered to fully-buffered when output is not direct to a TTY; but if it doesn't do this, that's a bug you should report to your OS vendor).


To run a new copy of say for each line of output from leave:

leave | while IFS= read -r line; do say <<<"$line"; done

To wait until leave has some output, run say exactly once, then exit:

leave | { IFS= read -r line; say <<<"$line"; }

All of this can be put in the background if you choose. For example:

{ leave | { IFS= read -r line; say <<<"$line"; }
} </dev/null >leave-say.log 2>&1 & disown -h "$!"

...will do an equivalent of running the above code in the background with nohup, writing any errors to leave-say.log instead of nohup.out.

CodePudding user response:

What leave does is fork a background process that gives the leaving messages on STDOUT. When the fork is done, the child process inherits the STDOUT from its parent. When the parent dies, the child keeps the STDIN of the next process in the pipe open. Therefore,

leave  0002  | sed 's/o/OOOO/g'

seems to hang until the child process is killed.

The correct solution would be to background the rest of the pipe:

leave   0002  | sed 's/o/OOOO/g' &

which produces then:

Just OOOOne mOOOOre minute!
Time tOOOO leave!

Or, in your case:

leave   0002  |say &

Killing the child process will stop say form 'hanging'. The PID of the child is given in the message Alarm set for xx.xx. (pid 3311636). You can verify this by killing the child from a different window.

Not that, if you do not leave, the child process will not die, but will keep on nagging from time to time. This means that say will"

  • say "alarm set for pid (output from the parent)
  • hang for a while waiting for input
  • say "Just one more minute!" (output from the child)
  • hang for another minute
  • say "Time to leave" (output from the child)
  • hang for another minute
  • say "Time to leave" (output from the child)

(last two lines repeated until the child is killed)

This is consistent also with the Macos manpage for say that states:

If the input is a TTY, or if no text is specified, the typed input text will be spoken line by line

  • Related