atRun a command once at a later time
at queues a command to run once at a given time ('23:00', 'now + 30 minutes', '9am next monday'). The atd daemon runs it later, even after you log out. atq lists pending jobs, at -c shows what a job will run, and atrm cancels one.
at [-f FILE] [-q QUEUE] [-m] TIME | atq | at -c JOB | atrm JOB...Rotate an API key tonight at 23:00, outside business hours, without staying logged in.
echo '/opt/scripts/rotate-keys.sh'at reads the job's commands from stdin (or from -f FILE). Without a pipe it opens an at> prompt; end it with Ctrl+D.
at parses the time spec, then writes an executable shell script to /var/spool/cron/atjobs. The filename encodes the queue letter, the job number and the run time in minutes since the epoch. The script first recreates your current environment: every exported variable, your umask and a cd to the directory you were in. Then come your commands. at then signals atd (via its PID file in /run) to look again. atd sleeps until the next due job, checks /etc/at.allow and at.deny, runs the script with /bin/sh as your user, captures its output and mails it to you through the local MTA. Jobs are stored on disk, so they survive reboots and run when atd starts again. If atd isn't running, at still queues the job but warns that it can't signal the daemon. Queue 'a' is the default; batch uses 'b', and running jobs show as '='.
TIMEHH:MM, noon, midnight, now + N minutes|hours|days, 9am next monday, 10:30 2026-11-02-f FILERead the job's commands from FILE instead of stdin-mMail when done, even without output-q LETTERUse queue LETTER (a default, b = batch, later letters = nicer)-l / atqList your pending jobs (root sees everyone's)-c JOBPrint the full script a job will run-r JOB / atrm JOBDelete a pending job-t [[CC]YY]MMDDhhmmExact time in touch-style formatat 23:00 ./cleanup.shat doesn't take the command as an argument. It reads commands from stdin, so './cleanup.sh' is parsed as part of the time and fails.
echo '/home/kunal/cleanup.sh' | at 23:00 # or: at -f cleanup.sh 23:00Using bash syntax ([[ ]], arrays, source) in an at jobJobs run under /bin/sh (dash on Ubuntu).
echo 'bash /home/kunal/job.sh' | at now + 1 hourAssuming the job ran because at accepted itIf atd isn't running, jobs just sit in the queue. Output is mailed, and without an MTA you see nothing.
systemctl is-active atd; redirect output: cmd >> /tmp/job.log 2>&1Using at for something that must happen every nightat runs once. Re-queueing from inside the job is fragile.
crontab -e or a systemd timerTry these on a Linux machine of your own. Each task has a hint and a solution.
Schedule a job that writes 'hello from at' to /tmp/at-test.txt in 2 minutes. Check that it's queued.
Show the full script that job will run and find the line that changes directory.
Queue a job for tomorrow at noon, then cancel it.
Wait for the first job to run and confirm its output.
atRun a command once at a later time
at queues a command to run once at a given time ('23:00', 'now + 30 minutes', '9am next monday'). The atd daemon runs it later, even after you log out. atq lists pending jobs, at -c shows what a job will run, and atrm cancels one.
at [-f FILE] [-q QUEUE] [-m] TIME | atq | at -c JOB | atrm JOB...Rotate an API key tonight at 23:00, outside business hours, without staying logged in.
echo '/opt/scripts/rotate-keys.sh'at reads the job's commands from stdin (or from -f FILE). Without a pipe it opens an at> prompt; end it with Ctrl+D.
at parses the time spec, then writes an executable shell script to /var/spool/cron/atjobs. The filename encodes the queue letter, the job number and the run time in minutes since the epoch. The script first recreates your current environment: every exported variable, your umask and a cd to the directory you were in. Then come your commands. at then signals atd (via its PID file in /run) to look again. atd sleeps until the next due job, checks /etc/at.allow and at.deny, runs the script with /bin/sh as your user, captures its output and mails it to you through the local MTA. Jobs are stored on disk, so they survive reboots and run when atd starts again. If atd isn't running, at still queues the job but warns that it can't signal the daemon. Queue 'a' is the default; batch uses 'b', and running jobs show as '='.
TIMEHH:MM, noon, midnight, now + N minutes|hours|days, 9am next monday, 10:30 2026-11-02-f FILERead the job's commands from FILE instead of stdin-mMail when done, even without output-q LETTERUse queue LETTER (a default, b = batch, later letters = nicer)-l / atqList your pending jobs (root sees everyone's)-c JOBPrint the full script a job will run-r JOB / atrm JOBDelete a pending job-t [[CC]YY]MMDDhhmmExact time in touch-style formatat 23:00 ./cleanup.shat doesn't take the command as an argument. It reads commands from stdin, so './cleanup.sh' is parsed as part of the time and fails.
echo '/home/kunal/cleanup.sh' | at 23:00 # or: at -f cleanup.sh 23:00Using bash syntax ([[ ]], arrays, source) in an at jobJobs run under /bin/sh (dash on Ubuntu).
echo 'bash /home/kunal/job.sh' | at now + 1 hourAssuming the job ran because at accepted itIf atd isn't running, jobs just sit in the queue. Output is mailed, and without an MTA you see nothing.
systemctl is-active atd; redirect output: cmd >> /tmp/job.log 2>&1Using at for something that must happen every nightat runs once. Re-queueing from inside the job is fragile.
crontab -e or a systemd timerTry these on a Linux machine of your own. Each task has a hint and a solution.
Schedule a job that writes 'hello from at' to /tmp/at-test.txt in 2 minutes. Check that it's queued.
Show the full script that job will run and find the line that changes directory.
Queue a job for tomorrow at noon, then cancel it.
Wait for the first job to run and confirm its output.