Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

In tcsh, the built-in sched command causes a command to be executed by the current shell at a specified time.

I have the following $HOME/.sched file (this is a simplified version of it):

setenv today `date +%F`
sched +00:01 sched 00:00 source $HOME/.sched

I then source $HOME/.sched in my $HOME/.cshrc.

This sets the environment variable $today to, for example, "2012-06-25", and automatically updates it to the current date every night at midnight. The job automatically reschedules itself every time it runs.

Note that the date command is invoked only once each day, and only when it's needed.

Is there a way to do this in bash? Note that the at command won't work; it invokes a command externally and cannot affect the current shell's environment.

(I know that I can type $(date +%F) rather than $today, but since I use this interactively, the extra typing is a significant burden.)

tcsh also has a number of special aliases that are executed automatically in certain circumstances:

The beepcmd, cwdcmd, periodic, precmd, postcmd, and jobcmd Special aliases can be set, respectively, to execute commands when the shell wants to ring the bell, when the working directory changes, every tperiod minutes, before each prompt, before each command gets executed, after each command gets executed, and when a job is started or is brought into the foreground.

Does bash have anything like these?

share|improve this question

6 Answers 6

I've come up with a solution that works without firing off an extra process on every prompt. Like 0xAF's answer, it uses $PROMPT_COMMAND. It's a bit convoluted, but it seems to work.

There are three steps:

First, I created a script called update-dot-today. I put it in $HOME/bin.

Here's the script:

#!/bin/bash

dir=$HOME/.today
mkdir -p $dir
file_count=$(ls $dir | wc -l)
today=$(date +%F)

if [ $file_count -eq 1 ] ; then
    if [ ! -e $dir/$today ] ; then
        mv $dir/* $dir/$today
    fi
else
    mv $dir $dir-$$
    mkdir -p $dir
    touch $dir/$today
    rm -rf $dir-$$
fi

When this is run, it ensures that the file $HOME/.today/YYYY-MM-DD exists, and that nothing else exists under $HOME/.today. Once the file is created, the script will just rename the existing file as needed, avoiding any race conditions.

Second, I added two entries to my crontab:

@reboot         $HOME/bin/update-dot-today
0 0 * * *       $HOME/bin/update-dot-today

This ensures that the file is created on boot and updated every night at midnight local time.

Third, I added the following to my $HOME/.bashrc:

today=$(date +%F)
if [ -d $HOME/.today ] ; then
    PROMPT_COMMAND='if [ ! -f "$HOME/.today/$today" ] ; then today=$(ls $HOME/.today) ; fi'
fi

This checks for the appropriate $HOME/.today/YYYY-MM-DD file -- but if it already exists and its name matches the current value of $today, the only cost is a file existence check.

If you prefer, the YYYY-MM-DD file can be placed somewhere other than /tmp/today, perhaps under your home directory. (In fact that might be safer, in case something cleans up /tmp between reboots, or in case another user on the system uses the same approach).

The only drawback (apart from the fact that it's rather complicated) is that the value of $today is set only after a shell prompt is printed. But that's just a matter of remembering to type an extra Enter at least once after midnight.

share|improve this answer
    
I think I have a better solution that doesn't require using crontab. I'll update this answer (and delete this comment) once I've verified that it actually works. –  Keith Thompson Jun 16 at 18:04

This might give you an idea for a solution:

$ trap 'today=$(date)' 10
$ echo $today
$ sleep 8
$ kill -10 $$
$ echo $today

Now you can use at to send yourself ($$) a signal at the turn of the day.

share|improve this answer

If you're willing trade two backquotes for a dollar:

alias today='date +%F'
echo `today`

If you want the date in a variable, then updating it in PROMPT_COMMAND is the only way I can think of. You can avoid forking a process at each prompt by using the built-in variable SECONDS.

update_today () {
  if ((SECONDS/86400 > __last_today_update)); then
    ((__last_today_update = SECONDS/86400))
    today=$(date +%F)
  fi
}
PROMPT_COMMAND="update_today; $PROMPT_COMMAND"

Another approach is to insert your text through a key binding.

rl_insert () {
  READLINE_LINE=${READLINE_LINE:0:$READLINE_POINT}$1${READLINE_LINE:$READLINE_POINT}
  ((READLINE_POINT+=${#1}))
}
bind -x '"\C-xt":rl_insert "$(date +%F)"'
share|improve this answer
    
the readline approach is interesting, thanks for sharing. –  0xAF Jun 27 '12 at 11:13
    
The expression ((SECONDS/86400)) will change at 24-hour intervals after the shell started, not at midnight; / is truncating integer division (and $SECONDS is the number of seconds since the shell started). –  Keith Thompson Mar 4 '13 at 21:46

It doesn't look like bash has an equivalent feature, at least not with all of the qualities you describe. If you're looking for a "best of both worlds", zsh has sched features.

I do realize that might be just as unhelpful as the other answers though.

share|improve this answer

It sounds to me like you're looking for at:

~$ at now + 1 minute
warning: commands will be executed using /bin/sh
at> echo "test" | wall
at> 
job 2 at Mon Jun 25 17:51:00 2012

Broadcast Message from user@host                                              
        (somewhere) at 17:51 ...                                               

test                                                                           

Though, I just reread your question, and you're looking for something that will re-self-schedule itself. That'd be pretty easy to do with at calling a shell script that does what it needs to do and then sets up a new at job. However, at does run in its own subshell; not as the shell you invoke it from.

share|improve this answer
    
As I said in the question, at won't work; it can't set an environment variable in the shell from which it's invoked. –  Keith Thompson Jun 26 '12 at 1:54

Have you tried $PROMPT_COMMAND ? It is commonly used to set the xterm title to show CWD for example. It is executed when the prompt is shown. It may be possible to set your variable with this. The first disadvantage would be that your command will be executed everytime you press enter in the shell. The second would be that if are not active on your terminal for long time and the day passes, it will not get automatically set. It will get set after next enter on terminal.

# export PROMPT_COMMAND="export today=\`date\`"
# echo $today
Tue Jun 26 01:07:19 EEST 2012
# echo $today
Tue Jun 26 01:07:21 EEST 2012
# echo $today
Tue Jun 26 01:07:22 EEST 2012
share|improve this answer
    
That's likely to be the best I can do. The first disadvantage is what I'm most concerned about. We'll see of anyone else comes up with a better solution. –  Keith Thompson Jun 25 '12 at 22:15
    
unfortunately bash does not have a pre-command (if we consider $PROMPT_COMMAND as a post-command). –  0xAF Jun 25 '12 at 22:18
    
Pre- vs. post- doesn't matter much; I can live with $today not being set correctly for the first command I execute after midnight. –  Keith Thompson Jun 26 '12 at 1:56

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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