12

I use Ubuntu server 16.04 and I desire to use the utility at in my current session to do something 1 minute from now (say, an echo), without giving a specific date and time - just 1 minute ahead from current time.

This failed:

echo 'hi' | at 1m

The reason I choose at over sleep is because sleep handicaps current session and is therefor more suitable to delay commands in another session, rather than the one we work with most of the time. AFAIR, at doesn't behave this way and won't handicap my session.

Update_1

By Pied Piper's answer, I've tried:

(sleep 1m; echo 'hi')  &

I have a problem with this method: The "hi" stream is printed inside my primary prompt and also adds an empty secondary prompt (_) right under the primary prompt that contains it, see:

USER@:~# (sleep 1m; echo 'hi')  &
[1] 22731
USER@:~# hi
^C
[1]+  Done

Update_2

By Peter Corde's answer I tried:

(sleep 2 && echo -e '\nhi' && kill -WINCH $$ &)

This works properly in Bash 4.4, but not in some older versions, seemingly (see comments in the answer). I personally use Bash 4.3 in my own environments.

  • 2
    I wish the kill flags were GNU-style and could be rearranged so it doesn't sound like "kill the wench"! – NH. Dec 5 '17 at 4:04
6
+100

The "hi" stream is printed in my prompt (in the stdin) and not in my stdout

No, it's not "printed in your stdin".

If you pressed return, it wouldn't try to run hi as a command. It's just that the backgrounded echo printed hi while the cursor was after your prompt.


Perhaps you want to print a leading newline, like (sleep 1m && echo -e '\nhi' &). (Adjust as necessary for the echo in your shell. Or use printf for portability.)

I put the & inside the subshell to "disown" the background job, so you also avoid the "noise" of [1]+ Done. With && between the commands, & applies to the whole compound-command chain, so it returns to the shell prompt right away instead of waiting for sleep to exit like you'd get with (sleep 1; echo ... &)

Here's what happens (with 1 second delay):

peter@volta:~$ (sleep 1 && echo -e '\nhi' &)
peter@volta:~$ 
hi
       # cursor is at the start of this empty line.

Bash doesn't know that echo printed something, so it doesn't know it needs to re-print its prompt or clean up the screen. You can do that manually with control-L.

You could write a shell function that gets bash to re-print its prompt after echo finishes. Just doing echo "$PS1" won't reproduce any already-typed characters. kill -WINCH $$ on my system gets bash to re-print its prompt line without clearing the screen, leaving hi on a line by itself before the prompt. SIGWINCH is sent automatically when the WINdow size CHanges, and bash's response to it happens to do what we want.

It might work with other shells, but I'm not attempting to make this 100% portable / POSIX. (Unfortunately, this only works on bash4.4, not bash4.3, or depends on some setting I'm unaware of)

  # bash4.4
peter@volta:~$ (sleep 2 && echo -e '\nhi' && kill -WINCH $$ &)
peter@volta:~$ ljlksjksajflasdf dfas      # typed in 2 seconds
hi
peter@volta:~$ ljlksjksajflasdf dfas      # reprinted by Bash because of `kill -WINCH`

You could easily wrap this up in a shell function that takes a sleep arg and a message.

bash 4.3 doesn't re-print its prompt after SIGWINCH, so this doesn't work there. I have shopt -s checkwinsize enabled on both systems, but it only works on the Bash 4.4 system (Arch Linux).

If it doesn't work, you could try (sleep 2 && echo -e '\nhi' && echo "$PS1" &), which "works" if the command line is empty. (It also ignores the possibility that PROMPT_COMMAND is set.)


There's no really clean way to get terminal output to mix with whatever you might be doing on your terminal later, when the timer fires. If you're in the middle of scrolling something in less, you could easily miss it.

If you're looking for something with interactive results, I suggest playing an audio file. e.g. with a command-line player like mpv (nice fork of MPlayer2). Or pick a video file so a new window opens.

(sleep 1 && mpv /f/share/music/.../foo.ogg </dev/null &>/dev/null &)

You might want to have your echo open a new xterm / konsole / gnome-terminal. Use an option that keeps the terminal open after the command exits.

  • I thank you dearly David, and I've thumbed up. This is really the closest I got regarding what I seek. Is there a way to upgrade the following command so to emulate an Enter key press event, right after the echo appears, so I would automatically be brought back into the console's primary prompt? (sleep 2 && echo -e '\nhi' && kill -WINCH $$ &). – Arcticooling Dec 5 '17 at 1:28
  • @Arcticooling: kill -WINCH $$ does refresh the prompt of the shell running in the terminal. That was the entire point of using it. Pressing enter would submit a partially-typed command, but WINCH doesn't, as I showed. If you haven't typed anything, then you get an empty prompt. – Peter Cordes Dec 5 '17 at 1:37
  • 1
    If you'd started to type rm -rf ~/some/directory, Enter at the wrong time would potentially run rm -rf ~/. (Safety tip: finish typing paths and then control-a and change ls to rm -rf, so fat-fingering Enter at any time won't ruin your day.) – Peter Cordes Dec 5 '17 at 1:38
  • Wired, I executed (sleep 2 && echo -e '\nhi' && kill -WINCH $$ &) and after the echo appeared I got an empty prompt, only after hitting Enter I got back to the primary prompt... Sorry if I misunderstand you, I'm quite new to Bash. – Arcticooling Dec 5 '17 at 1:50
  • @Arcticooling: I'm using Bash 4.4 on Arch Linux, in a Konsole terminal emulator. I hoped my command would be portable at least to other versions of bash, but maybe not :( Yeah, just tested in Bash 4.3 inside a screen session (and just through SSH) on an older Ubuntu system, and I got the behaviour you describe. – Peter Cordes Dec 5 '17 at 1:54
13

The correct at usage is at <timespec>, where <timespec> is the time specification. You can also use the -f option to specify the file containing the commands to execute. If -f and no redirection is used, at will read the commands to execute from the stdin (standard input).

In your example, to execute "some command" (I chose "some command" to be "echo hi" in the example below) one minute right after you hit enter (now), the intended <timespec> to be used is now + 1 minute. So, in order to obtain the result you want with a one-liner solution, please use the command below:

echo 'echo hi' | at now + 1 minute

This is supposed (and will do) to run the echo hi command after one minute. The problem here is that the echo hi command will be executed by the at command in a dummy tty and the output will be sent to the user's mail box (if it is correctly configured - which requires a more extensive explanation on how to configure a mail box in a unix machine and is not part of the scope of this question). The bottomline is that you won't be able to directly see the result of echo hi in the same terminal you issued the command. The easiest alternative is you redirect it to "some file", for example:

echo 'echo hi > /tmp/at.out' | at now + 1 minute

In the above example, "some file" is /tmp/at.out and the expected result is that the file at.out under /tmp is created after one minute and contais the text 'hi'.

In addition to the now + 1 minute example above, many other time specifications can be used, even some complex ones. The at command is smart enough to interpret pretty human readable ones like the examples below:

now + 1 day, 13:25, mid‐night, noon, ...

Please refer to at's man page for more about the possible time specifications as the possible variations are pretty extensive and may include dates, relative or absolute times, etc..

  • 2
    at by default sends the output via mail, but this needs to be properly configured to work. – Simon Richter Dec 4 '17 at 23:44
  • I now offer 100 rep bounty for an answer. Please read my bounty message below and edit to explain more on what is at <timespec> and the flag you used and if it suits Ubuntu 16.04 xenial. – Arcticooling Dec 7 '17 at 20:00
  • Ok, I increased the level of detail in the answer so that it is more didactic now. It may sound a little long-winded for some, but I believe it leaves no margin for doubts as it clearly cites examples that work and how they work as well. – Marcelo Dec 8 '17 at 1:31
7

Run the sleep in a subshell:

(sleep 60; echo -e '\nhi')  &

Note: on Linux you can give the argument to sleep in seconds or with a suffix s/m/h/d (for seconds, minutes, hours, days). On BSD the argument has to be in seconds

  • I have a small problem with this method --- the "hi" stream is printed in my prompt (in the stdin) and not in my stdout (as it would with a regular echo). – Arcticooling Dec 4 '17 at 20:30
  • 1
    If you don't want the output in your terminal mixed up with prompts and the output of other commands you'll have to redirect it somewhere – PiedPiper Dec 4 '17 at 20:32
  • This redirection failed @PiedPiper (sleep 10s; echo 'hi') & 1>. I now read more on this. – Arcticooling Dec 4 '17 at 20:37
  • 2
    Reading the documentation is always a good idea :) – PiedPiper Dec 4 '17 at 20:39
  • 1
    @Arcticooling It seems like you're confused about the meanings of stdin and stdout. They have nothing to do with where things appear on your terminal; instead, you should interpret them with respect to a particular process (basically, a program). Standard input ("stdin") for a process is a source from which the process can read data, and the process's standard output ("stdout") is a destination to which it can write data. These sources and destinations can be other programs, or files, or the terminal itself (stuff you type goes to stdin, and anything that comes to stdout is displayed). – David Z Dec 4 '17 at 22:26
3

That failed because you're piping the output of echo "hi", which is just hi to at, but at expects that it's input can be executed by the default system shell (usually bash, but sometimes something different depending on the system).

This:

at 1m << EOF
echo "hi"
EOF

will achieve what you appear to be trying to do. It uses a shell construct called a 'here document', which is generally the accepted way to do this type of thing (because it handles multiple lines better than using the echo command and doesn't require creating a new file like you would need to with cat), and translates to the somewhat more complicated equivalent using echo:

echo 'echo "hi"' | at 1m

It is probably worth noting that at will mail any command-output to the user who invoked the at command, instead of wirting output to the terminal as a command delayed using sleep in a subshell would. In particular, this means that unless you've done some customization of the system, or intend to read your local mail spool, you won't be able to get the output of commands run via at.

  • For some reason, both example you gave give me an error syntax error. Last token seen: m Garbled time . I have no idea why. I use Ubuntu 16.04, Bash 4.0. Maybe you did that on another system. Even when I type just at without any further arguments --- I still get this same error. More data here. – Arcticooling Dec 4 '17 at 21:31
  • 1
    at 1m doesn't work for a posix compatible at. The command would need to be at now + 1 minute – PiedPiper Dec 4 '17 at 21:31
  • @PiedPiper is correct, 1m isn't compatible with POSIX compliant versions of at, I was just assuming that you had a version that accepted that syntax. – Austin Hemmelgarn Dec 5 '17 at 14:53
  • @PiedPiper, at 1m is not POSIX, but POSIX compatible implementations of at are free to interpret it as they wish, it doesn't make an at implementation less compliant to interpret it as meaning the same as now + 1 minute than returning an error or meaning one month ago. IOW, it's the at 1m code that is not POSIX. – Stéphane Chazelas Dec 5 '17 at 14:59
2

Combine @Peter Cordes's answer and this answer https://stackoverflow.com/questions/23125527/how-to-return-to-bash-prompt-after-printing-output-from-backgrounded-function/23125687#23125687

The code below is from the latter answer, with a little change of mine. Compile it to file a.out (whatever name you like)

#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
        char *tty = "/dev/tty";
        if (argc > 1)
                tty = argv[1];
        int fd = open(tty, O_WRONLY);

        int i;
        char buf[] = "\n";
        for (i = 0; i < sizeof buf - 1; i++)
                if (ioctl(fd, TIOCSTI, &buf[i]))
                        perror("ioctl");
        close(fd);
        return 0;
}

Then run the command below. (I put a.out in dir /tmp/, you may need to change to yours)

(sleep 2 && echo -e '\nhi' && /tmp/a.out &)

or

(sleep 2 && echo -e '\nhi' && /tmp/a.out /proc/$$/fd/0 &)

BTW, kill -WINCH $$ works just fine for me with Bash 4.3 on Gentoo.

1

Building upon the answers above, you can have the embedded command direct its output to your terminal, like so:

echo "echo hi >$(tty); kill -WINCH $$" | at now + 1 minute

The tty command returns the device path of your current terminal, enclosing it in $(...) has bash execute it in a sub-shell, and the kill command sends a signal to the current process that will have bash re-print its $PS1 prompt.

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.