Cron服务我是一直有在使用的,比如开机启动,比如定时任务,这在构建自己的平台的时候是非常常见的需求,而且大多数情况下Cron服务表现的非常出色,但并不是没有缺点和不适用的场景。Inotify tools是一个工具集,里面包含三个常用的命令,功能是实时监控指定的文件或者文件夹,每当发生变化,即使非常细微,inotify也能及时的识别并报告出来。可以看出这两者差别还是挺大的,这就意味着互补性挺强的。
cron
服务是Linux系统元老级的服务进程了,很多系统服务也会依赖于该服务。它的功能是固定周期定时循环执行某个命令或者脚本,如果想要在未来某个时间执行一次动作,可以考虑at
命令,这个命令用法比较简单,使用man
看下手册就知道该怎么使用,在这里就不做记录了。主要使用到的命令是crontab
,本身的用法也比较简单,只不过有些规则比较绕,很容易设置完不生效。
crontab
命令是用来为不同用户安装,卸载以及列出使用cron
服务的工具,每个用户可以单独设定自己的crontab
文件各个用户的文件都被放在/var/spool/cron/crontabs
文件夹中(Debian11系统), 一般情况下,十分不建议手动编辑。
crontab
命令的用户,每行一个用户crontab
命令的用户,每行一个用户cron.allow
为准Option | Descrition |
---|---|
-e | edit,编辑对应用户的crontab文件 |
-r | remove,移除对应用户的crontab文件,谨慎使用 |
-i | interactive,配合-r使用,删除文件前确认 |
-l | list,列出对应用户的crontab文件内容 |
-u | user,指定用户设定或者列出crontab文件内容 |
/var/spool/cron/crontabs
文件夹下的crontab文件内容分为三部分,环境变量设定,cron命令以及注释,每一部分的内容都是以行为单位,不可交叉,环境变量一般放在文件开头部分。需要注意,虽然crontab文件本身定义了一些环境变量(详见命令的man
手册),但是最好还是自行将使用到的环境变量写在文件中,以避免运行不生效;另外,该文件不会做通常的bash替换,所以不能识别当前环境下的变量值。
bashPATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/var/lib/snapd/snap/bin:/root/bin"
SHELL="/usr/bin/bash"
# * * * * *
# - - - - -
# | | | | |
# | | | | +----- 星期几 (0 - 7) (Sunday=0 or 7, Sunday on this platform)
# | | | +---------- 月份 (1 - 12)
# | | +--------------- 几号 (1 - 31)
# | +-------------------- 小时 (0 - 23)
# +------------------------- 分钟 (0 - 59)
# at the beginning of every hour, update blog content & backup mysql
50 * * * * /usr/bin/bash /opt/scripts/sql_backup.sh
# at 02:10 of every day, do rclone sync things
10 2 * * * /usr/bin/bash /opt/scripts/rclone/rclone_sync.sh
10 2 * * * /usr/bin/bash /opt/scripts/hosts_update.sh
# at 03:20 of every Sunday, do backup things
20 3 * * 7 /usr/bin/bash /opt/scripts/backups.sh >> /opt/logs/backups.log 2>&1
# every half day, at 00:00 or 12:00, will do the transfermation between dos and unix format of all .md file
0 */12 * * * /usr/bin/find /opt/ -regextype posix-extended -regex ".*\.log|.*\.conf|.*\.sh|.*\.md" -exec /usr/bin/dos2unix {} \; > /dev/null 2>&1
# 00:05 of every day, do force reload rclone & alist service to refresh contents
5 0 * * * /usr/bin/bash /opt/scripts/rclone/rclone_alist_automount.sh -c
# 5 0 * * * /usr/bin/bash /opt/scripts/rclone/rclone_cloudreve_automount.sh -c
5 0 * * * /usr/bin/bash /opt/scripts/rclone/rclone_onedrive_automount.sh -c
# every mimute, check if there is any file name or number change in /opt/media.koel
# * * * * * /usr/bin/bash /opt/scripts/koel_update.sh
# every 10 mimutes, check if rclone & alist service is normal or not
*/10 * * * * /usr/bin/bash /opt/scripts/rclone/rclone_alist_automount.sh
# */10 * * * * /usr/bin/bash /opt/scripts/rclone/rclone_cloudreve_automount.sh
*/10 * * * * /usr/bin/bash /opt/scripts/rclone/rclone_onedrive_automount.sh
# do repo update @ 01:30 everyday
30 1 * * * /usr/bin/bash /opt/scripts/github_update.sh >> /opt/logs/github_update.log 2>&1
# reboot every Thursday 00:00
# 0 0 * * 4 /usr/sbin/reboot
# when reboot
@reboot /usr/bin/aria2c --conf-path=/etc/aria2/aria2c.conf -D
@reboot /usr/local/bin/jupyter lab --allow-root
@reboot /usr/bin/python3 /opt/source-code/calibre-web/cps.py
@reboot /usr/bin/qbittorrent-nox
@reboot /usr/bin/mount -t ext4 -w /dev/sdb1 /opt/webdav/wd
@reboot /usr/bin/bash /opt/scripts/jekyll_update.sh >> /opt/logs/jekyll_update.log
# @reboot /opt/source-code/clash/clash
# NO, because rclone depends alist.service
# @reboot nohup /usr/bin/rclone mount ALIST:/ /opt/webdav --allow-other --vfs-cache-mode full --vfs-cache-max-size 10G --vfs-read-ahead 100M --vfs-cache-max-age 4h --cache-dir /tmp/vfs-cache --bwlimit-file 20M --bwlimit 100M --log-file /opt/logs/rclone.log --log-level NOTICE --vfs-read-chunk-size-limit 10m --buffer-size 50M --attr-timeout 5m --transfers=6 --multi-thread-streams=6 --allow-non-empty > /dev/null 2>&1 &
# # acme auto renew cert
43 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null
如上述的文件实例内容,cron命令的格式为时间周期 + 命令
,命令不需要多解释,可以是单行命令,也可以是一个脚本的执行;对于时间周期,crontab
提供了丰富的格式,以应对不同的使用场景。
text# * * * * * # - - - - - # | | | | | # | | | | +----- 星期几 (0 - 7) (Sunday=0 or 7, Sunday on this platform) # | | | +---------- 月份 (1 - 12) # | | +--------------- 几号 (1 - 31) # | +-------------------- 小时 (0 - 23) # +------------------------- 分钟 (0 - 59)
通常情况下,周期时间是用五个数字来表示的,不设定的情况下是五个星(* * * * *),代表的含义以及对应的可选范围如上。根据不同的系统发行版,有些系统还支持使用星期和月份的缩写,来替代数字,比如Mar
表示三月,Fri
表示星期五等等。另外对于需要跟随系统启动,或者每天,每周,每月,每年运行一次的任务,还可以使用如下:
除了如上指定的形式,crontab
还支持更加多样化的规则设定,有些系统发行版支持执行列表和范围同时存在,比如"1,5, 10-40 * * * *",但是有些不支持,需要根据使用的系统来判断。
bash# 每5min打印一次hello
*/5 * * * * COMMAND
# 每8h执行一次命令,00:00,08:00和16:00
0 */8 * * * COMMAND
# 仅在01:00,09:00和12:00执行命令
0 1,9,12 * * * COMMAND
# 在每年3-5月份的4号凌晨03:05 -- 03:30之间,每分钟执行一次命令
5-30 3 4 3-5 * COMMAND
# 每小时的第一分钟,第五分钟以及第10-40分钟,每分钟执行一次命令
1,5, 10-40 * * * * COMMAND
一般情况下可以通过cron
命令来设定需要记录日志的级别,如下图所示,可以使用-L loglevel
参数来指定该服务需要记录日志的内容,loglevel为设定的等级,可有想要设定的内容代表的数字累加而得到。默认是level 1,只记录开始的动作;设定为0,表示关闭服务的日志记录功能;设定为15,表示开启所有类型的日志记录,相应的,如果服务下有很多指令,还是建议不要开启全日志记录,会占用比较大的空间。
日志存放路径:
/var/log/syslog
也不是最近的需求了,其实这个博客系统也是使用Jekyll构建的,每次有新的博文增加或者修改都需要手动运行一下jekyll b -s ...
,虽然提供了--watch --incremental
参数可以实时监控博文的更新修改情况,一种情况是修改_config.yml时,不生效,需要停止进程重新运行;另一种情况是,运行一段时间之后还是会发现某些博文没有被及时更新上线。直到最近在学习使用Just the Docs构建知识体系,发现实时监控文件变化的诉求更加迫切。
inotify tools是Linux内核2.6.13 (June 18, 2005)版本新增的一个子系统(API),它提供了一种监控文件系统(基于inode的)事件的机制,可以监控文件系统的变化如文件修改、新增、删除等,并可以将相应的事件通知给应用程序。这个工具集包含如下三个命令,统计使用最多的是initofywait
,因为在收集资料时发现,绝大部分现有的资料都是针对inotifywait
,而且不能说完全相同吧,只能说绝大部分是Copy & Paste。
inotify-tools是一个C库和一组命令行组成的工具集,提供了Linux下inotify工作的简单接口。也是一种强大的、细粒度的、异步文件系统监控机制,它满足各种各样的文件监控需要,可以监控文件系统的访问属性、读写属性、权限属性、删除创建、移动等操作,因为调用的是内核的API,可以监控文件发生的一切变化。
该命令的作用就是使用inotify
接口监控文件或者文件夹的变化,特别适合用在shell脚本中,它可以被设定成触发一次之后退出,也可以被设定成持续监控并输出触发的结果。
支持的选项
Options | Descriptions |
---|---|
@ | 监控文件夹时,排除部分文件夹,绝对或相对路径取决于被监控文件夹的格式 |
--fromfile | 从文件中读取要监控的文件夹,以@开头的行表示排除在外 |
-m, --monitor | 持续监控而不是触发一次就退出 |
-d, --daemon | 同 -m,不过不会输出在stdout上,而是输出到文件,所以必须指定-o参数 |
-o, --output | 输出触发的事件内容到文件 |
-s, --syslog | 把错误信息输出到syslog而不是stderr |
-r. --recursive | 递归监控文件夹,没有深度限制,即使新创建的文件夹也会被监控,软链接除外 |
-q, --quiet | 安静模式,-q只输出事件信息,-qq什么都不会输出 |
--exclude | 排除文件或者文件夹,可使用扩展正则表达式,区分大小写 |
--excludei | 排除文件或者文件夹,可使用扩展正则表达式,不区分大小写 |
−t , −−timeout | 如果指定时间内没有触发,就会退出,不指定则无限等待 |
−e , −−event | 指定要监听的事件,可指定多个,逗号分隔 |
−−timefmt | 指定输出时间的格式,必须要遵守strftime规则,同date |
−−format | 类似于printf的语法,自定义输出触发事件的格式 |
对于--format <fmt>
参数,用户可以根据如下指定格式进行自定义输出,每行限制4000个字符:
−−timefmt <fmt>
参数指定的时间格式特别需要注意:--exclude(i)
参数在命令行中只能使用一次,使用多次的话,结果是只会执行最后一次的表达式指定的文件或者目录;@
参数后面直接跟要排除的文件夹的路径,且只能是文件夹路径,两者之间没有空格;--fromfile
后面跟一个文件,文件内容是要监控的文件夹路径或者要排除的文件夹路径。所以@
和--fromfile
这两个参数只能处理文件夹路径,不能排除文件,从加粗的频率看,博主大概率是在这方面吃过亏的QAQ!!
总结一下,关于排除文件和文件夹,--exclude(i)
参数既可以排除文件,也可以排除文件夹,还可以使用扩展正则表达式,功能最全面;@
和--fromfile
这两个参数只能处理文件夹路径,不能排除文件;有一种情况--fromfile
后面的文件内容可以是文件路径,那就是有很多零散的文件需要监控,这个时候就肯定没有也没必要有需要排除的文件或者文件夹了,因为指定的就是文件路径,不想监控,可以直接不写。
bash# 很明显watch.list里面放的是文件夹,因为使用了 -r 参数
# 如下inotify命令表示,监控 /root/TODO/ 文件夹,但是排除其下的exclude/,yes/ 和 nope/ 文件夹
# 也排除文件名是todo.txt的文件,而且排除的是所有todo.txt文件
$ cat /root/watch.list
/root/TODO
@/root/TODO/exclude
$ inotifywait -mrq --fromfile /root/watch.list --timefmt "%Y-%m-%d %H:%M:%S" --format "%T %w%f %e" -e delete,move,close_write --exclude '/root/TODO/nope/|^.*/todo.txt' @/root/TODO/yes
可被监控的事件类型
Event Name | Description |
---|---|
access | 监控之下的文件被读取 |
modify | 监控之下的文件被修改 |
attrib | 监控之下的文件属性发生变化,权限,日期信息等 |
close_write | 监控之下的文件被可写模式打开后关闭,但这并不意味着文件有写入 |
close_nowrite | 监控之下的文件被只读模式打开后关闭 |
close | 无特殊意义,不单独出现,一般跟在上述两种关闭事件之后,表示文件关闭 |
open | 监控之下的文件被打开 |
move_to | 文件或者文件夹被移动到了被监控的目录下,被监控路径下的移动都属于此类 |
move_from | 文件或者文件夹被移动到了被监控的目录之外,被监控路径下的移动也都属于此类 |
move | 表示包含上述两种移动事件,不会打印出现 |
move_self | - |
create | 监控之下,文件或者文件夹被创建 |
delete | 监控之下,文件或者文件夹被删除 |
delete_self | - |
unmount | 被监控的文件或者文件夹所在的文件系统被unmount之后触发 |
一般常用的事件类型是create/delete/modify/open/close_write/move,因为inotifywait
监控的粒度非常的详细,所以每操作一个文件的时候,命令都会输出详细的步骤,体现在程序中就是一次文件修改/创建操作,导致循环被执行多次,造成一定程度的资源浪费,合理选择触发的事件类型可以一定程度上规避此问题。
输出格式
inotifywait
命令会将启动时的采集分析信息输出到stderr,把触发的事件信息输出到stdout,默认的输出格式是按照输出,每行的组成分别是"watched_filename EVENT_NAMES event_filename"
textwatched_filename: 被监控的文件名,如果监控的是目录,则是触发事件的文件所在目录,最后一个字符一定是'/' EVENT_NAMES : 触发的事件名称,可参考上述"事件类型" event_filename : 该部分仅当监控目录的时候才会输出,是触发事件的文件名
inotifywatch
命令的主要功能是使用inotify接口对某个目录下的文件操作事件类型数量做统计分析,该命令也是监控文件的各种变化,但是并不执行命令,而只是将每个文件触发的事件类型次数做了一个统计,并且事件类型统计可以按照自定义格式排序。该命令可被监控的事件类型和inotifywait
完全一致,命令支持的参数比inotifywait
没有-m/-d/-o/-s
,但是多两个-a/-d
,仅作不同之处的记录。
Options | Descriptions |
---|---|
-a , --ascending | 按照某种事件类型升序统计 |
-d , --descending | 按照某种事件类型降序统计 |
bash# 监控/root/TODO文件夹,并按照access升序统计
[root@CrossChain] [~]
> inotifywatch -r -a access /root/TODO/ -e modify,open,create,access -t 60
Establishing watches...
Finished establishing watches, now collecting statistics.
total access modify open filename
383 7 4 372 /root/TODO/nope/
411 16 4 391 /root/TODO/just-the-docs-0.4.0.rc3/
1116 29 4 1083 /root/TODO/
# 监控/root/TODO文件夹,并按照access降序统计
[root@CrossChain] [~]
> inotifywatch -r -d access /root/TODO/ -e modify,open,create,access -t 60
Establishing watches...
Finished establishing watches, now collecting statistics.
total access modify open filename
1296 38 6 1252 /root/TODO/
582 20 5 557 /root/TODO/yes/
216 9 2 205 /root/TODO/just-the-docs-0.4.0.rc3/
该命令Man手册中解释的功能是:inotify−hookable − blocking command−line interface to inotify。我理解应该是该命令提供了一个阻塞命令行执行的接口,当文件被检测到有变化才会触发式执行。据命令的开发者描述,这个小工具是为了替代“the functionality offered by Plack’s Filesys::Notify::Simple”,简化了原来需要写脚本,①文件系统发生变化,②inotify
检测到变化,③执行指定文件变化时需要运行的命令,而现在使用inotify-hookable
就可以在一个命令中完成之前需要三步才能完成的事情。
而且相较于inotifywait/inotifywatch
(使用的时notify7接口),该命令使用inotify2接口,Man Page中描述,该接口速度非常的快,以至于需要处理好该命令发送事件类型的速度,以保证其他程序可以正确收到信号并执行。
支持的选项
Event Name | Description |
---|---|
-w, --watch-directories | 指定需要监控的文件夹,可使用多次来监控不同的文件夹 |
-f, --watch-files | 指定需要监控的文件,可使用多次来监控不同的文件,与-w不冲突 |
-r, --[no-]recursive | 对指定的文件夹执行递归操作,默认开启 |
-c, --on-modify-command | 当文件改变时,命令会执行 |
-C, --on-modify-path-command | 是一个键值对的形式,key代表一个扩展正则表达式,value表示对匹配改正则表达式的文件或者文件夹需要执行的命令 |
-t, --buffer-time | 该命令执行的非常快,当某个命令的操作可能造成被检测为两个批次,所以添加了等待时间100ms,如果100ms内没有接收到其他的事件触发,则立即发送当前的事件 |
-i, --ignore-paths | 使用正则表达式指定不需要监控的文件或者文件夹 |
-d, --debug | debug模式,输出的信息更加详细 |
-q, --[no-]quiet | 安静模式,输出简洁的信息 |
如下是两个示例程序,理论上inotify-hookable
命令后面可以监控非常多的文件和文件夹,只要注意使用--on-modify-path-command
和--on-modify-command
这两个选项设定好命令执行范围即可。另外,同一个脚本中只能有一个inotify-hookable
命令出现,不能出现多个。该命令大多数情况下可以替代多个inotifywait
脚本,实现统一的管理。
bash# 示例程序-1
inotify-hookable \
--watch-directories /opt/source-code/blog \
--watch-directories /opt/source-code/document/python \
--watch-directories /root/TODO \
--ignore-paths /opt/source-code/blog/.git/ \
--ignore-paths /opt/source-code/blog/img/avatar.jpg \
--ignore-paths /root/TODO/nope \
--ignore-paths /root/TODO/.*\.txt \
--on-modify-path-command "(^/opt/source-code/blog/.*)=(echo blog)" \
--on-modify-path-command "(^/opt/source-code/document/python/.*)=(echo python)" \
--on-modify-path-command "(^/root/TODO/.*)=(echo TODO)"
# 示例程序-2
inotify-hookable \
--watch-files /root/TODO/nope/todo.txt \
--on-modify-path-command "(^/root/TODO/nope)=(echo todo)" \
--watch-files /root/TODO/yes/hook.log \
--on-modify-command "echo hook" \
虽然理论上inotify-hookable
命令可以将多个inotifywait
命令写的脚本整合成一个脚本,实际面临着一个问题,使用inotifywait
命令写的脚本一般都会有一定的长度,因为涉及到业务逻辑处理,所以需要在脚本-1中定义好功能实现函数,根据传参确定执行的部分,然后在脚本-2的inotify-hookable
命令中调用脚本-1,如下是一个真实的例子,实际执行的时候,会将对应的部分展开执行。最后,运行结果表明,inotify-hookable
的执行速度远远快于inotifywait
命令!!!
bash# 脚本-1
#!/bin/bash
# update Just the Docs -- Python
function python_update() {
echo ====================================================
echo `date`
echo $directory$filename $action
rm -rf /opt/websites/just-the-docs/python
jekyll b -s /opt/source-code/document/python -d /opt/websites/just-the-docs/python
echo -e '\n'
}
# update Jekyll blog
function blog_update() {
echo ====================================================
echo `date`
echo $directory$filename $action
rm -rf /opt/websites/blog
let numOfAvatar=`ls /opt/websites/nav/assets/images/logos/ | wc -l`
let randNumber=$RANDOM%$numOfAvatar
cp /opt/websites/nav/assets/images/logos/${randNumber}.jpg /opt/websites/homepage/assets/img/logo.jpg -rf
cp /opt/websites/nav/assets/images/logos/${randNumber}.jpg /opt/websites/nav/assets/images/logos/avatar.jpg -rf
cp /opt/websites/nav/assets/images/logos/${randNumber}.jpg /opt/source-code/blog/img/avatar.jpg -rf
jekyll b -s /opt/source-code/blog/ -d /opt/websites/blog/
echo -e '\n'
}
if [[ $1 == 'blog' ]]; then
blog_update
elif [[ $1 == 'python' ]]; then
python_update
else
echo Wrong
fi
# ===========================================================
# 脚本-2
#!/bin/bash
# 监控文件变化,自动生成内容
inotify-hookable \
--watch-directories /opt/source-code/blog \
--watch-directories /opt/source-code/document/python \
--ignore-paths /opt/source-code/blog/.git/ \
--ignore-paths /opt/source-code/blog/img/avatar.jpg \
--on-modify-path-command "(^/opt/source-code/blog/.*)=($(blog_update))" \
--on-modify-path-command "(^/opt/source-code/document/python/.*)=($(python_update))"
该特性在Linux内核2.6.13版本以后,才能支持inotify软件。在没有安装inotify软件之前,在"/proc/sys/fs/inotify"文件夹下应该有max_queued_events/max_user_instances/max_user_watches
这三个文件,这三个文件中的默认值都是可以被修改的,以监听更大范围的文件,但在修改之前,要确保自己知道在做什么。
文件 | 默认 | 描述 |
---|---|---|
max_queued_events | 16384 | 设置inotify实例事件队列可容纳的事件数量 |
max_user_instances | 128 | 设置每个用户可以运行的inotifywait或inotifywatch命令的进程数 |
max_user_watches | 65536 | 设置inotifywait或inotifywatch命令可以监视的文件数量(单进程) |
默认是Debian11系统
本文作者:Manford Fan
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!