我将制作一个在启动时执行并定期运行的 bash 脚本。
我希望它是用户可配置的,以便用户可以0 * * * * my_script
通过运行添加 cron 作业my_script add 0 * * * *
,通过 列出作业my_script list
,并通过命令my_script remove job_number
输出中列出作业编号的位置删除作业my_script list
。
如果我可以单独管理 crontab 文件,这将很容易实现。然而,似乎 crontab 对于每个用户来说只有一个文件(如果不是,请告诉我)。当然,直接处理该 crontab 文件是一个糟糕的解决方案。
那么处理 cron 作业的正确方法是什么?或者,是否有更好的方法来处理定期运行的脚本?
状况:
- 任何用户都应该能够运行它,无论是否有特权。
- 没有依赖性。
附加问题:
由于我找不到任何适当的方法来管理定期运行的脚本,我想我可能做错了什么。从软件设计的意义上来说,实现一个接口来管理软件的定时任务是不是不切实际?我应该将所有日程管理留给用户吗?
答案1
在大多数 Unix 系统上,使用 cron 是安排任务定期运行的正确方法。使用个人 crontab 是用户安排自己的任务的最方便的方法。系统任务可以由 root 调度(不使用下面的脚本!)在系统 crontab 中,其格式通常略有不同(带有用户名的额外字段)。
这是给您的一个简单脚本。任何用户都可以使用它来管理自己的个人 crontab。
它不会对其输入进行任何类型的验证,除非您给它的参数太少,它会抱怨。因此,完全有可能添加格式不正确的 crontab 条目。
该
remove
子命令采用行号,并将删除 crontab 中该行上的内容,无论它是什么。该号码未经消毒直接传递至sed
。当您添加一个 crontab 条目时,必须用引号引起来。这会影响您处理报价的方式里面crontab 条目本身。
大多数问题对您来说应该相对容易解决。
#!/bin/sh
usage () {
cat <<USAGE_END
Usage:
$0 add "job-spec"
$0 list
$0 remove "job-spec-lineno"
USAGE_END
}
if [ -z "$1" ]; then
usage >&2
exit 1
fi
case "$1" in
add)
if [ -z "$2" ]; then
usage >&2
exit 1
fi
tmpfile=$(mktemp)
crontab -l >"$tmpfile"
printf '%s\n' "$2" >>"$tmpfile"
crontab "$tmpfile" && rm -f "$tmpfile"
;;
list)
crontab -l | cat -n
;;
remove)
if [ -z "$2" ]; then
usage >&2
exit 1
fi
tmpfile=$(mktemp)
crontab -l | sed -e "$2d" >"$tmpfile"
crontab "$tmpfile" && rm -f "$tmpfile"
;;
*)
usage >&2
exit 1
esac
使用示例:
$ ./script
Usage:
./script add "job-spec"
./script list
./script remove "job-spec-lineno"
$ ./script list
1 */15 * * * * /bin/date >>"$HOME"/.fetchmail.log
2 @hourly /usr/bin/newsyslog -r -f "$HOME/.newsyslog.conf"
3 @reboot /usr/local/bin/fetchmail
$ ./script add "0 15 * * * echo 'hello world!'"
$ ./script list
1 */15 * * * * /bin/date >>"$HOME"/.fetchmail.log
2 @hourly /usr/bin/newsyslog -r -f "$HOME/.newsyslog.conf"
3 @reboot /usr/local/bin/fetchmail
4 0 15 * * * echo 'hello world!'
$ ./script remove 4
$ ./script list
1 */15 * * * * /bin/date >>"$HOME"/.fetchmail.log
2 @hourly /usr/bin/newsyslog -r -f "$HOME/.newsyslog.conf"
3 @reboot /usr/local/bin/fetchmail
答案2
您当前的 cron 实现可能支持 /etc/cron.d,其中作业在常规时间字段之后和命令之前有一个额外的“运行身份”用户指定。因此,只需调整您的界面以在该目录中创建文件,就像常规 cron 条目一样,并在第五个字段之后添加用户名(可能从登录用户名中提取)。然后您可以为每个文件执行一项作业。 :)
看着man 5 crontab
https://linux.die.net/man/5/crontab
请注意,这样做的主要问题是,可以编辑该目录中文件的用户可以创建一个以 root 身份运行的文件。因此,您可能需要一个通过 sudo 运行的包装脚本,它将验证输入并将计算出的用户名强制写入生成的文件中。然后需要确保您在脚本中安全地执行操作,例如不信任 $PATH 等。
PS,在shell脚本中:getent passwd $(</proc/self/loginuid)
老实说,如果您要让用户了解 cron 时间格式,那么多花几秒钟来教他们使用crontab -e
($EDITOR
如果 vi 太可怕的话还需要设置)并不是非常困难。
答案3
如果你想要一个 cron 条目:
0 * * * * my_script
那么我建议您为 cron 管理函数考虑一个单独的名称,例如“cron_mgmt”,其第一个参数是 CASE 语句中的 SWITCH:
cron_mgmt () {
case $1 in
add ) ... ;;
list ) ...;;
remove ) ...
help ) ...
* ) echo "You need help. Here it is"
cron_mgmt help
;;
esac
}
答案4
不要推出自己的界面,而是保持简单。要安装具有特定计划的 cron 作业,请将其放置在特定目录中。要删除它,请将其从目录中删除。您不需要为用户提供任何工具来执行此操作,只需告诉他们将作业放入哪个目录即可。
这就是大多数 Linux 发行版管理可由各个软件包安装的系统 cron 作业的方式。 Debian、Fedora、Arch Linux 和其他操作系统都提供了一个名为 的工具,run-parts
其工作是逐个运行特定目录中的脚本。不同的实现run-parts
对于脚本可接受的文件名有不同的规则。如果您不想依赖于此,您可以自己推出:
for x in /path/to/daily-jobs/*; do
if [ -x "$x" ]; then
case "${x##*/}" in
*[!-0-9A-Z_a-z]*) :;; # skip file names containing "weird" characters
*) "$x";;
esac
fi
done
如果你想让用户指定他们自己的时间表,那么这种方法就行不通。每次提交新作业时都需要重建 crontab 文件。这是可以做到的,但可靠地设置起来很复杂。但是,如果用户可以指定自己的计划,那么就没有理由在 之上添加另一个界面,或者在后台crontab -e
调用的 GUI 。crontab -e