编辑
2023-02-21
Command
00
请注意,本文编写于 623 天前,最后修改于 417 天前,其中某些信息可能已经过时。

目录

一、Cron定时任务
1. 相关文件
2. 命令选项
3. 文件内容
4. cron命令书写规则
5. 日志记录
二、Inotify实时文件监控
1. 简介
2. 命令使用
a. inotifywait
b. inotifywatch
c. inotify-hookable
3. 参数配置
三、参考文档

Cron服务我是一直有在使用的,比如开机启动,比如定时任务,这在构建自己的平台的时候是非常常见的需求,而且大多数情况下Cron服务表现的非常出色,但并不是没有缺点和不适用的场景。Inotify tools是一个工具集,里面包含三个常用的命令,功能是实时监控指定的文件或者文件夹,每当发生变化,即使非常细微,inotify也能及时的识别并报告出来。可以看出这两者差别还是挺大的,这就意味着互补性挺强的。

一、Cron定时任务

cron服务是Linux系统元老级的服务进程了,很多系统服务也会依赖于该服务。它的功能是固定周期定时循环执行某个命令或者脚本,如果想要在未来某个时间执行一次动作,可以考虑at命令,这个命令用法比较简单,使用man看下手册就知道该怎么使用,在这里就不做记录了。主要使用到的命令是crontab,本身的用法也比较简单,只不过有些规则比较绕,很容易设置完不生效。

crontab命令是用来为不同用户安装,卸载以及列出使用cron服务的工具,每个用户可以单独设定自己的crontab文件各个用户的文件都被放在/var/spool/cron/crontabs文件夹中(Debian11系统), 一般情况下,十分不建议手动编辑。

1. 相关文件

  • /etc/cron.allow: 记录允许使用crontab命令的用户,每行一个用户
  • /etc/cron.deny: 记录不允许使用crontab命令的用户,每行一个用户
  • 两个文件只存在其一,以存在的文件为准
  • 两个文件都不存在,以系统设定为准,都不允许或者都允许,root用户始终被允许
  • 两个文件都存在,则以cron.allow为准

2. 命令选项

OptionDescrition
-eedit,编辑对应用户的crontab文件
-rremove,移除对应用户的crontab文件,谨慎使用
-iinteractive,配合-r使用,删除文件前确认
-llist,列出对应用户的crontab文件内容
-uuser,指定用户设定或者列出crontab文件内容

3. 文件内容

/var/spool/cron/crontabs文件夹下的crontab文件内容分为三部分,环境变量设定,cron命令以及注释,每一部分的内容都是以行为单位,不可交叉,环境变量一般放在文件开头部分。需要注意,虽然crontab文件本身定义了一些环境变量(详见命令的man手册),但是最好还是自行将使用到的环境变量写在文件中,以避免运行不生效;另外,该文件不会做通常的bash替换,所以不能识别当前环境下的变量值。

bash
PATH="/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

4. cron命令书写规则

如上述的文件实例内容,cron命令的格式为时间周期 + 命令,命令不需要多解释,可以是单行命令,也可以是一个脚本的执行;对于时间周期,crontab提供了丰富的格式,以应对不同的使用场景。

text
# * * * * * # - - - - - # | | | | | # | | | | +----- 星期几 (0 - 7) (Sunday=0 or 7, Sunday on this platform) # | | | +---------- 月份 (1 - 12) # | | +--------------- 几号 (1 - 31) # | +-------------------- 小时 (0 - 23) # +------------------------- 分钟 (0 - 59)

通常情况下,周期时间是用五个数字来表示的,不设定的情况下是五个星(* * * * *),代表的含义以及对应的可选范围如上。根据不同的系统发行版,有些系统还支持使用星期和月份的缩写,来替代数字,比如Mar表示三月,Fri表示星期五等等。另外对于需要跟随系统启动,或者每天,每周,每月,每年运行一次的任务,还可以使用如下:

  • @hourly :每小时运行一次,相当于"0 * * * *"
  • @midnight :同@daily
  • @daily :一天运行一次,相当于"0 0 * * *"
  • @weekly :一周运行一次,相当于"0 0 * * 0"
  • @monthly :一个月运行一次,相当于"0 0 1 * *"
  • @annually :一年运行一次,相当于"0 0 1 1 *"
  • @yearly :同@annually
  • @reboot :重启时运行一次

除了如上指定的形式,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

5. 日志记录

一般情况下可以通过cron命令来设定需要记录日志的级别,如下图所示,可以使用-L loglevel参数来指定该服务需要记录日志的内容,loglevel为设定的等级,可有想要设定的内容代表的数字累加而得到。默认是level 1,只记录开始的动作;设定为0,表示关闭服务的日志记录功能;设定为15,表示开启所有类型的日志记录,相应的,如果服务下有很多指令,还是建议不要开启全日志记录,会占用比较大的空间。

日志存放路径:/var/log/syslog

二、Inotify实时文件监控

也不是最近的需求了,其实这个博客系统也是使用Jekyll构建的,每次有新的博文增加或者修改都需要手动运行一下jekyll b -s ...,虽然提供了--watch --incremental参数可以实时监控博文的更新修改情况,一种情况是修改_config.yml时,不生效,需要停止进程重新运行;另一种情况是,运行一段时间之后还是会发现某些博文没有被及时更新上线。直到最近在学习使用Just the Docs构建知识体系,发现实时监控文件变化的诉求更加迫切。

1. 简介

inotify tools是Linux内核2.6.13 (June 18, 2005)版本新增的一个子系统(API),它提供了一种监控文件系统(基于inode的)事件的机制,可以监控文件系统的变化如文件修改、新增、删除等,并可以将相应的事件通知给应用程序。这个工具集包含如下三个命令,统计使用最多的是initofywait,因为在收集资料时发现,绝大部分现有的资料都是针对inotifywait,而且不能说完全相同吧,只能说绝大部分是Copy & Paste

2. 命令使用

inotify-tools是一个C库和一组命令行组成的工具集,提供了Linux下inotify工作的简单接口。也是一种强大的、细粒度的、异步文件系统监控机制,它满足各种各样的文件监控需要,可以监控文件系统的访问属性、读写属性、权限属性、删除创建、移动等操作,因为调用的是内核的API,可以监控文件发生的一切变化。

a. inotifywait

该命令的作用就是使用inotify接口监控文件或者文件夹的变化,特别适合用在shell脚本中,它可以被设定成触发一次之后退出,也可以被设定成持续监控并输出触发的结果。

支持的选项

OptionsDescriptions
@监控文件夹时,排除部分文件夹,绝对或相对路径取决于被监控文件夹的格式
--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个字符:

  • %w:监控文件时,输出为触发事件的文件
  • %f:监控文件夹时,输出为触发事件的文件,否则为空字符串
  • %e:输出为触发的事件名称,多个事件以逗号分隔
  • %Xe:输出为触发的事件名称,多个事件用字符"X"分隔
  • %T:输出为使用−−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 NameDescription
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"

text
watched_filename: 被监控的文件名,如果监控的是目录,则是触发事件的文件所在目录,最后一个字符一定是'/' EVENT_NAMES : 触发的事件名称,可参考上述"事件类型" event_filename : 该部分仅当监控目录的时候才会输出,是触发事件的文件名

b. inotifywatch

inotifywatch命令的主要功能是使用inotify接口对某个目录下的文件操作事件类型数量做统计分析,该命令也是监控文件的各种变化,但是并不执行命令,而只是将每个文件触发的事件类型次数做了一个统计,并且事件类型统计可以按照自定义格式排序。该命令可被监控的事件类型和inotifywait完全一致,命令支持的参数比inotifywait没有-m/-d/-o/-s,但是多两个-a/-d,仅作不同之处的记录。

OptionsDescriptions
-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/

c. inotify-hookable

该命令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 NameDescription
-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, --debugdebug模式,输出的信息更加详细
-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中定义好功能实现函数,根据传参确定执行的部分,然后在脚本-2inotify-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))"

3. 参数配置

该特性在Linux内核2.6.13版本以后,才能支持inotify软件。在没有安装inotify软件之前,在"/proc/sys/fs/inotify"文件夹下应该有max_queued_events/max_user_instances/max_user_watches这三个文件,这三个文件中的默认值都是可以被修改的,以监听更大范围的文件,但在修改之前,要确保自己知道在做什么。

文件默认描述
max_queued_events16384设置inotify实例事件队列可容纳的事件数量
max_user_instances128设置每个用户可以运行的inotifywait或inotifywatch命令的进程数
max_user_watches65536设置inotifywait或inotifywatch命令可以监视的文件数量(单进程)

默认是Debian11系统

三、参考文档

如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:Manford Fan

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!