安装命令

默认 Ubuntu 或 Debian 是没有预装 at 命令的,我们需要先用以下命令安装,其它的操作系统或发行版,可遵照 Command not found 网站的说明进行安装。

$ sudo apt install at
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following NEW packages will be installed:
  at
0 upgraded, 1 newly installed, 0 to remove and 174 not upgraded.
Need to get 39.5 kB of archives.
After this operation, 155 kB of additional disk space will be used.
Get:1 http://mirrors.aliyun.com/ubuntu bionic/main amd64 at amd64 3.1.20-3.1ubuntu2 [39.5 kB]
Fetched 39.5 kB in 0s (237 kB/s)
Selecting previously unselected package at.
(Reading database ... 423876 files and directories currently installed.)
Preparing to unpack .../at_3.1.20-3.1ubuntu2_amd64.deb ...
Unpacking at (3.1.20-3.1ubuntu2) ...
Setting up at (3.1.20-3.1ubuntu2) ...
Created symlink /etc/systemd/system/multi-user.target.wants/atd.service → /lib/systemd/system/atd.service.
Processing triggers for man-db (2.8.3-2ubuntu0.1) ...
Processing triggers for ureadahead (0.100.0-21) ...
Processing triggers for systemd (237-3ubuntu10.54) ...

检查 Atd 服务状态

At 命令需要有后台服务 atd 作支撑,其提供时间解析、存储、定时执行、查询、删除等功能接口给 at 调用。
因此,在安装完毕后,让我们检查一下 atd 服务启动状态:

$ sudo systemctl status atd
● atd.service - Deferred execution scheduler
   Loaded: loaded (/lib/systemd/system/atd.service; enabled; vendor preset: enabled)
   Active: active (running) since Sun 2022-09-04 21:55:04 CST; 57s ago
     Docs: man:atd(8)
 Main PID: 13929 (atd)
    Tasks: 1 (limit: 6143)
   CGroup: /system.slice/atd.service
           └─13929 /usr/sbin/atd -f

Sep 04 21:55:04 dell-7920-tower systemd[1]: Started Deferred execution scheduler.

从服务日志可以看到,At Daemon 的延迟(定时)执行的调度器已经启动,并且已经设置了开机自启动。

设置开机自启动

如果在上一步没有显示开机自启动,可用下面的命令启用:

sudo systemctl enable --now atd

使用方法

命令用法

$ which at
/usr/bin/at

$ at --help
Usage: at [-V] [-q x] [-f file] [-mMlbv] timespec ...
       at [-V] [-q x] [-f file] [-mMlbv] -t time
       at -c job ...
       atq [-V] [-q x]
       at [ -rd ] job ...
       atrm [-V] job ...
       batch

不带参数

$ at
Garbled time

At 提示我们未指定时间或时间有误,接下来我们在命令后面附加时间参数。

指定时间提交作业

先看一眼时钟,确认当前时间。

$ date
Sun Sep 4 21:58:12 CST 2022

添加一条命令,它将在晚上十点钟(绝对时间),写入当前时间日期到主目录下 .myjobs 文件中。

(base) muwaii@dell-7920-tower:~$ at 10pm
warning: commands will be executed using /bin/sh
at> echo `date` > /home/muwaii/.myjobs
at> <EOT>
job 1 at Sun Sep  4 22:00:00 2022

在添加命令时,一次性可以输入多个命令或脚本地址,最后需要以 ctrl+d 作为结尾,告诉 at 结束输入并保存定时作业。如果输入的命令错了,可按 ctrl+c 取消本次输入,重新用 at 命令输入。

注:
At 命令中对于时间的格式是有要求的,大体上分为绝对时间和相对时间,在正文中仅介绍常见的格式,关于完整的定义读者可参见附录。

查询已提交的作业

输出已经提交了,但是还没有执行的作业列表。注意,已经执行过的作业不会出现在这里!

$ atq
1       Sun Sep  4 22:00:00 2022 a muwaii

输出的内容,从左到右,分别是:

作业编号 待执行时间 任务队列名称 提交该命令的用户名
1 Sun Sep 4 22:00:00 2022 a muwaii

可通过 -q 参数来指定加入的队列,候选值为 26 个字母之一。

查看命令的上下文

查看执行该命令的上下文环境,内容比较长,一般看最后一段中待执行的命令明文即可。前面的运行检查、环境变量等,可以先不细究,留着在命令执行出错或未符合预期时排错使用。

$ at -c 1
#!/bin/sh
# atrun uid=1001 gid=1001
# mail muwaii 0
umask 2
CUDNN_INSTALL_DIR=/usr/local/cuda; export CUDNN_INSTALL_DIR
CONDA_SHLVL=1; export CONDA_SHLVL
LIBVA_DRIVER_NAME=iHD; export LIBVA_DRIVER_NAME
LC_ALL=en_US.utf8; export LC_ALL
CONDA_EXE=/home/muwaii/anaconda3/bin/conda; export CONDA_EXE
SSH_CONNECTION=10.1.10.210\ 63875\ 10.1.10.251\ 22; export SSH_CONNECTION
VIRTUALENVWRAPPER_WORKON_CD=1; export VIRTUALENVWRAPPER_WORKON_CD
LIBVA_DRIVERS_PATH=/opt/intel/mediasdk/lib64; export LIBVA_DRIVERS_PATH
VIRTUALENVWRAPPER_HOOK_DIR=/home/.envs; export VIRTUALENVWRAPPER_HOOK_DIR
LANG=en_US.utf8; export LANG
WORKON_HOME=/home/.envs; export WORKON_HOME
CONDA_PREFIX=/home/muwaii/anaconda3; export CONDA_PREFIX
NOCONDA_PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin; export NOCONDA_PATH
TR_PATH=/home/muwaii/tensorrt/TensorRT-7.0.0.11; export TR_PATH
_CE_M=; export _CE_M
XDG_SESSION_ID=489; export XDG_SESSION_ID
USER=muwaii; export USER
VIRTUALENVWRAPPER_VIRTUALENV=/usr/local/bin/virtualenv; export VIRTUALENVWRAPPER_VIRTUALENV
PWD=/home/muwaii; export PWD
HOME=/home/muwaii; export HOME
CONDA_PYTHON_EXE=/home/muwaii/anaconda3/bin/python; export CONDA_PYTHON_EXE
SSH_CLIENT=10.1.10.210\ 63875\ 22; export SSH_CLIENT
CUDA_HOME=/usr/local/cuda; export CUDA_HOME
XDG_DATA_DIRS=/usr/local/share:/usr/share:/var/lib/snapd/desktop; export XDG_DATA_DIRS
OPENNI2_REDIST=/home/muwaii/pcl/projects/3dReconstruction/OpenNI_2.3.0.63/Linux/OpenNI-Linux-x64-2.3.0.63/Redist; export OPENNI2_REDIST
_CE_CONDA=; export _CE_CONDA
OPENNI2_INCLUDE=/home/muwaii/pcl/projects/3dReconstruction/OpenNI_2.3.0.63/Linux/OpenNI-Linux-x64-2.3.0.63/Include; export OPENNI2_INCLUDE
CONDA_PROMPT_MODIFIER=\(base\)\ ; export CONDA_PROMPT_MODIFIER
SSH_TTY=/dev/pts/6; export SSH_TTY
VIRTUALENVWRAPPER_PYTHON=/usr/local/bin/python3.8; export VIRTUALENVWRAPPER_PYTHON
MAIL=/var/mail/muwaii; export MAIL
MFX_HOME=/opt/intel/mediasdk; export MFX_HOME
VIRTUALENVWRAPPER_SCRIPT=/usr/local/bin/virtualenvwrapper.sh; export VIRTUALENVWRAPPER_SCRIPT
SHLVL=1; export SHLVL
LANGUAGE=en_US.utf8; export LANGUAGE
LOGNAME=muwaii; export LOGNAME
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1001/bus; export DBUS_SESSION_BUS_ADDRESS
XDG_RUNTIME_DIR=/run/user/1001; export XDG_RUNTIME_DIR
VIRTUALENVWRAPPER_PROJECT_FILENAME=.project; export VIRTUALENVWRAPPER_PROJECT_FILENAME
PATH=/home/muwaii/anaconda3/bin:/home/muwaii/anaconda3/condabin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/muwaii/.dotnet/tools:/usr/local/cuda/bin:/home/muwaii/tensorrt/TensorRT-7.0.0.11/bin:/usr/local/bin/:/home/muwaii/.local/bin; export PATH
PKG_CONFIG_PATH=:/usr/local/lib/pkgconfig; export PKG_CONFIG_PATH
CONDA_DEFAULT_ENV=base; export CONDA_DEFAULT_ENV
CUDA_INSTALL_DIR=/usr/local/cuda; export CUDA_INSTALL_DIR
cd /home/muwaii || {
         echo 'Execution directory inaccessible' >&2
         exit 1
}
echo `date` > /home/muwaii/.myjobs

删除作业

在定时的一次性任务还没有执行/触发之前,我们可以将其删除。

$ at noon + 5 MINUTE
warning: commands will be executed using /bin/sh
at> echo `ssh-agent -s` > /home/muwaii/.ssh_agents
at> <EOT>
job 2 at Mon Sep  5 12:05:00 2022
$ atq
2       Mon Sep  5 12:05:00 2022 a muwaii
$
$
$ atrm 2
$ atq
$ atrm 2
Cannot find jobid 2

可见,我们先是创建了一个在中午(12点) + 5 分钟(相对时间)的定时作业,接着我们使用 atq 确认该作业确实已提交。紧随其后,我们再用 atrm 命令结合 at 创建作业时或 atq 查询作业时返回的作业编号,将之前提交的编号为 2 的作业删除。最后,我们再次查询作业时,发现队列中空空如也,而且删除过的作业,是不能重复删除的,at 提示作业编号未找到。

附录之日期时间标准格式

标准格式中定义了合法的时间格式各个组成部分,而每个组成部分又可以包含更小的构件。所以,timespec 中以编译器文法的方式进行描述。

基本符号

首先是定义了常见符号,如

  • DOTTEDDATE: . 隔开的日期
  • HYPHENDATE: - 隔开的日期
  • NOW: 现在
  • AM, PM: 上午,下午
  • Noon, MidNight, TeaTime: 正午 12:00 (12:00 PM), 午夜 00:00 (12:00 AM), 下午茶 16:00 (04:00 PM)
  • SUN, MON, TUE, WED, THU, FRI, SAT: 星期几
  • TODAY, TOMORROW: 今天,明天
  • JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC: 12 个月之一
$ cat /usr/share/doc/at/timespec
/*
 * Abbreviated version of the yacc grammar used by at(1).
 */

%token  <charval> DOTTEDDATE
%token  <charval> HYPHENDATE
%token  <charval> HOURMIN
%token  <charval> INT1DIGIT
%token  <charval> INT2DIGIT
%token  <charval> INT4DIGIT
%token  <charval> INT5_8DIGIT
%token  <charval> INT
%token  NOW
%token  AM PM
%token  NOON MIDNIGHT TEATIME
%token  SUN MON TUE WED THU FRI SAT
%token  TODAY TOMORROW
%token  NEXT
%token  MINUTE HOUR DAY WEEK MONTH YEAR
%token  JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC
%token  UTC

时间日期类型

接着介绍了,24 小时和 12 小时格式的绝对时间日期类型,以及相对时间类型。

%type <charval> concatenated_date
%type <charval> hr24clock_hr_min
%type <charval> int1_2digit
%type <charval> int2_or_4digit
%type <charval> integer
%type <intval> inc_dec_period
%type <intval> inc_dec_number
%type <intval> day_of_week

timespec 语法

最后便是解析的主入口,开始自顶而下地将 timespec 分解成一个个组件,直到不能继续分的符号(Token)为止,比如五月 MAY 和下午 PM 就属于不可继续划分的原子单位。

%start timespec
%%
timespec        : spec_base
                | spec_base inc_or_dec
                ;

spec_base       : date
                | time
                | time date
                | NOW
                ;

time            : time_base
                | time_base timezone_name
                ;

time_base       : hr24clock_hr_min
                | time_hour am_pm
                | time_hour_min
                | time_hour_min am_pm
                | NOON
                | MIDNIGHT
                | TEATIME
                ;

hr24clock_hr_min: INT4DIGIT
                ;

time_hour       : int1_2digit
                ;

time_hour_min   : HOURMIN
                ;

am_pm           : AM
                | PM
                ;

timezone_name   : UTC
                ;

date            : month_name day_number
                | month_name day_number year_number
                | month_name day_number ',' year_number
                | day_of_week
                | TODAY
                | TOMORROW
                | HYPHENDATE
                | DOTTEDDATE
                | day_number month_name
                | day_number month_name year_number
                | month_number '/' day_number '/' year_number
                | concatenated_date
                | NEXT inc_dec_period
                | NEXT day_of_week
                ;

concatenated_date: INT5_8DIGIT
                ;

month_name      : JAN | FEB | MAR | APR | MAY | JUN
                | JUL | AUG | SEP | OCT | NOV | DEC
                ;

month_number    : int1_2digit
                ;

day_number      : int1_2digit
                ;

year_number     : int2_or_4digit
                ;

day_of_week     : SUN | MON | TUE | WED | THU | FRI | SAT
                ;

inc_or_dec      : increment | decrement
                ;

increment       : '+' inc_dec_number inc_dec_period
                ;

decrement       : '-' inc_dec_number inc_dec_period
                ;

inc_dec_number  : integer
                ;

inc_dec_period  : MINUTE | HOUR | DAY | WEEK | MONTH | YEAR
                ;

int1_2digit     : INT1DIGIT | INT2DIGIT
                ;

int2_or_4digit  : INT2DIGIT | INT4DIGIT
                ;

integer         : INT | INT1DIGIT | INT2DIGIT | INT4DIGIT | INT5_8DIGIT
                ;

%%

添加新评论