在本章中,我们将教您计划和记录脚本结果的基础知识。我们将首先解释如何使用at
和cron
来调度命令和脚本。在本章的第二部分,我们将描述如何记录脚本的结果。我们可以使用 Linux 的本地邮件功能和重定向来达到这个目的。
本章将介绍以下命令:at
、wall
、atq
、atrm
、sendmail
、crontab
和alias
。
本章将涵盖以下主题:
- 用
at
和cron
调度 - 记录脚本结果
本章所有脚本均可在 GitHub:https://GitHub . com/PacktPublishing/Learn-Linux-Shell-Scripting-Bash-4.4 基础/tree/master/Chapter14 上找到。剩下的例子和练习应该在你的 Ubuntu 虚拟机上进行。
到目前为止,我们已经了解了 shell 脚本世界中的许多东西:变量、条件、循环、重定向,甚至函数。在本章中,我们将解释另一个与 shell 脚本密切相关的重要概念:调度。
简而言之,调度就是确保您的命令或脚本在特定的时间运行,而不需要您每次都亲自启动它们。清理日志就是一个经典的例子;通常,较旧的日志不再有用,并且会占用太多空间。例如,您可以通过清除超过 45 天的日志的清理脚本来解决这个问题。然而,这样的脚本应该每天运行一次。在工作日,这不应该是最大的问题,但是在周末登录并不有趣。实际上,我们甚至不应该考虑这一点,因为调度允许我们定义或脚本运行的频率!
在 Linux 调度中,最常用的工具是at
和cron
。我们将首先使用at
描述调度的原理,然后继续使用功能更强大(也正因为如此,使用更广泛)的cron
。
at
命令主要用于临时调度。at
的语法非常接近我们的自然语言。这最容易用一个例子来解释,如下所示:
reader@ubuntu:~/scripts/chapter_14$ date
Sat Nov 24 11:50:12 UTC 2018
reader@ubuntu:~/scripts/chapter_14$ at 11:51
warning: commands will be executed using /bin/sh
at> wall "Hello readers!"
at> <EOT>
job 6 at Sat Nov 24 11:51:00 2018
reader@ubuntu:~/scripts/chapter_14$ date
Sat Nov 24 11:50:31 UTC 2018
Broadcast message from reader@ubuntu (somewhere) (Sat Nov 24 11:51:00 2018):
Hello readers!
reader@ubuntu:~/scripts/chapter_14$ date
Sat Nov 24 11:51:02 UTC 2018
本质上,你是在告诉系统:在<时间戳>做点什么。当您输入at 11:51
命令时,会出现一个交互式提示,允许您输入想要执行的命令。之后用 Ctrl + D 退出提示;如果使用 Ctrl + C ,作业不会被保存!作为参考,我们在这里使用一个简单的命令,wall
,它允许您向当时登录到服务器的每个人广播一条消息。
当你使用at
时,你可以绝对的指定时间,就像我们在前面的例子中做的那样,或者相对的。亲戚的一个例子是 5 分钟后的 T2 或 24 小时后的 T4。这通常比检查当前时间、添加您想要的时间间隔并将其传递给at
更容易。这使用以下语法:
reader@ubuntu:~/scripts/chapter_14$ at now + 1 min
warning: commands will be executed using /bin/sh
at> touch /tmp/at-file
at> <EOT>
job 10 at Sun Nov 25 10:16:00 2018
reader@ubuntu:~/scripts/chapter_14$ date
Sun Nov 25 10:15:20 UTC 2018
您总是需要指定要添加分钟、小时或天的相对时间。幸运的是,我们可以使用现在作为当前时间的关键词。请注意,在处理分钟时,at
将始终舍入到最近的整分钟。除了分钟,以下内容也是有效的(见man at
):
- 小时
- 天
- 周末
您甚至可以创建更复杂的解决方案,例如从现在起三天后的下午 4 点。然而,我们觉得cron
更适合这种情况。对于at
来说,最好的利用似乎是在附近的一次性工作。
一旦你开始安排工作,你会发现自己陷入了这样一种境地,要么把时间搞砸了,要么把工作内容搞砸了。对于某些工作,你可以添加一个新的,让另一个失败。然而,在某些情况下,原始作业会对您的系统造成严重破坏。在这种情况下,删除不正确的作业是个好主意。幸运的是,at
的创作者预见到了这个问题(大概也经历过吧!)并创建了这个功能。atq
命令(在 队列中为的缩写)向您显示当前在管道中的作业。有了atrm
(不要以为我们需要解释那一个),你可以通过数字来移除工作。让我们看一个队列中有多个作业的例子,并删除一个:
reader@ubuntu:~/scripts/chapter_14$ vim wall.txt
reader@ubuntu:~/scripts/chapter_14$ cat wall.txt
wall "Hello!"
reader@ubuntu:~/scripts/chapter_14$ at now + 5 min -f wall.txt
warning: commands will be executed using /bin/sh
job 12 at Sun Nov 25 10:35:00 2018
reader@ubuntu:~/scripts/chapter_14$ at now + 10 min -f wall.txt
warning: commands will be executed using /bin/sh
job 13 at Sun Nov 25 10:40:00 2018
reader@ubuntu:~/scripts/chapter_14$ at now + 4 min -f wall.txt
warning: commands will be executed using /bin/sh
job 14 at Sun Nov 25 10:34:00 2018
reader@ubuntu:~/scripts/chapter_14$ atq
12 Sun Nov 25 10:35:00 2018 a reader
13 Sun Nov 25 10:40:00 2018 a reader
14 Sun Nov 25 10:34:00 2018 a reader
reader@ubuntu:~/scripts/chapter_14$ atrm 13
reader@ubuntu:~/scripts/chapter_14$ atq
12 Sun Nov 25 10:35:00 2018 a reader
14 Sun Nov 25 10:34:00 2018 a reader
如您所见,我们为at
使用了一个新的标志:-f
。这允许我们运行文件中定义的命令,而不是必须使用交互式 Shell。我们以这个文件结尾。txt(为了清楚起见,不需要扩展),包含要执行的命令。我们使用这个文件来安排三个作业:5 分钟后、10 分钟后和 4 分钟后。完成后,我们使用atq
查看当前队列:所有三个作业,编号为 12、13 和 14。此时,我们意识到我们只希望作业在 4 分钟和 5 分钟后运行,而不是 10 分钟后运行。我们现在可以使用atrm
通过简单地将作业号添加到命令中来删除该作业号 13。当我们稍后再次查看队列时,我们看到只剩下作业 12 和 14。几分钟后,前两个你好!信息被打印到我们的屏幕上。如果我们等满 10 分钟,我们就知道了...没什么,因为我们已经成功删除了我们的工作:
Broadcast message from reader@ubuntu (somewhere) (Sun Nov 25 10:34:00 2018):
Hello!
Broadcast message from reader@ubuntu (somewhere) (Sun Nov 25 10:35:00 2018):
Hello!
reader@ubuntu:~/scripts/chapter_14$ date
Sun Nov 25 10:42:07 UTC 2018
Instead of using atq
and atrm
, at
also has flags we can use for those functions. For atq
, this is at -l
(list). atrm
even has two possible alternatives: at -d
(delete) and at -r
(remove). It does not matter whether you use the supporting commands or the flags; under the hood, the same thing will be executed. Use whatever is easiest to remember for you!
正如你可能已经注意到的,到目前为止,我们只使用了不依赖于 stdout 的命令(我们知道,有点偷偷摸摸)。然而,一旦你想一想,这就带来了一个真正的问题。通常,当我们处理命令和脚本时,我们使用 stdout/stderr 来获得对我们的操作结果的感觉。交互式提示也是如此:我们使用键盘通过 stdin 提供输入。既然我们安排了非互动工作,事情就不一样了。首先,我们不能再使用像read
这样的交互构造了。脚本将会失败,因为没有可用的 stdin。但是,同样,也没有可用的标准输出,所以我们甚至没有看到脚本失败!还是有?
在at
的联机帮助页的某个地方,您可以找到以下文本:
"The user will be mailed standard error and standard output from his commands, if any. Mail will be sent using the command /usr/sbin/sendmail. If at is executed from a su(1) shell, the owner of the login shell will receive the mail."
似乎at
的创作者也想到了这个问题。但是,如果你对 Linux 没有太多的经验(还没有!),您可能对前面文本的邮件部分感到困惑。如果你想到的是有邮票的那种,那你就大错特错了。然而,如果你认为电子邮件,你会更温暖一点。
在不涉及太多细节的情况下(这绝对超出了本书的范围),Linux 有一个本地邮件缓冲区,,允许你在本地系统内发送电子邮件。如果您用上游服务器配置它,您实际上也可以发送实际的电子邮件,但是现在,请记住 Linux 系统上的内部电子邮件是可用的。有了这个邮件缓冲区,电子邮件就成了文件系统上的文件(也许这并不奇怪)。这些可以在/var/spool/mail 找到,这实际上是/var/mail 的符号链接。如果您安装了 Ubuntu 18.04,这些目录将是空的。这很容易解释:默认情况下,不安装sendmail
。如果没有安装它,并且您计划了一个具有标准输出的作业,就会发生这种情况:
reader@ubuntu:/var/mail$ which sendmail # No output, so not installed.
reader@ubuntu:/var/mail$ at now + 1 min
warning: commands will be executed using /bin/sh
at> echo "Where will this go?"
at> <EOT>
job 15 at Sun Nov 25 11:12:00 2018
reader@ubuntu:/var/mail$ date
Sun Nov 25 11:13:02 UTC 2018
reader@ubuntu:/var/mail$ ls -al
total 8
drwxrwsr-x 2 root mail 4096 Apr 26 2018 .
drwxr-xr-x 14 root root 4096 Jul 29 12:30 ..
是的,没什么事。现在,如果我们安装sendmail
并再次尝试,我们应该会看到不同的结果:
reader@ubuntu:/var/mail$ sudo apt install sendmail -y
[sudo] password for reader:
Reading package lists... Done
<SNIPPED>
Setting up sendmail (8.15.2-10) ...
<SNIPPED>
reader@ubuntu:/var/mail$ which sendmail
/usr/sbin/sendmail
reader@ubuntu:/var/mail$ at now + 1 min
warning: commands will be executed using /bin/sh
at> echo "Where will this go?"
at> <EOT>
job 16 at Sun Nov 25 11:17:00 2018
reader@ubuntu:/var/mail$ date
Sun Nov 25 11:17:09 UTC 2018
You have new mail in /var/mail/reader
邮件,只给你!如果我们检查/var/mail/
reader@ubuntu:/var/mail$ ls -l
total 4
-rw-rw---- 1 reader mail 1341 Nov 25 11:18 reader
reader@ubuntu:/var/mail$ cat reader
From [email protected] Sun Nov 25 11:17:00 2018
Return-Path: <[email protected]>
Received: from ubuntu.home.lan (localhost.localdomain [127.0.0.1])
by ubuntu.home.lan (8.15.2/8.15.2/Debian-10) with ESMTP id wAPBH0Ix003531
for <[email protected]>; Sun, 25 Nov 2018 11:17:00 GMT
Received: (from reader@localhost)
by ubuntu.home.lan (8.15.2/8.15.2/Submit) id wAPBH0tK003528
for reader; Sun, 25 Nov 2018 11:17:00 GMT
Date: Sun, 25 Nov 2018 11:17:00 GMT
From: Learn Linux Shell Scripting <[email protected]>
Message-Id: <[email protected]>
Subject: Output from your job 16
To: [email protected]
Where will this go?
它甚至看起来像一封真正的电子邮件,有日期:,主题:,收件人:,和发件人:(等等)。如果我们安排更多的工作,我们会看到新的邮件附加到这个文件。Linux 有一些简单的、基于文本的邮件客户端,允许你把这个单个文件当作多个电子邮件来处理(这方面的一个例子是mutt
);然而,我们的目的并不需要这些。
One thing of note when dealing with notifications from the system, such as the You have new mail one, is that it does not always get pushed to your Terminal (while some others, such as wall
, do). These messages are printed the next time your Terminal is updated; this is often done when you enter a new command, (or just an empty Enter). If you're working on these examples and waiting for the output, don't hesitate to press Enter a few times and see whether something comes up!
虽然有时获得我们作为作业运行的命令的输出是很好的,但更多时候它可能非常烦人,因为许多进程可以向您发送本地邮件。通常,这将导致您不查看邮件的情况,甚至主动抑制命令的输出,因此您不会收到更多的邮件。在本章的后面,在我们介绍完cron
之后,我们将花一些时间描述我们如何以正确的方式处理输出*。作为一个小的预览,这意味着我们不会依赖像这样的内置功能,但是我们将使用重定向来将我们需要的输出写到我们知道可以找到它的地方。*
现在已经讨论了通过at
进行调度的基础知识,让我们来看看 Linux 上真正的调度发电站:cron
。cron
是一个作业调度程序,由两个主要组件组成: cron 守护程序(有时称为 crond )和 crontab 。cron 守护程序是运行计划作业的后台进程。这些作业是使用 crontab 计划的,crontab 只是文件系统上的一个文件,最常用同名命令进行编辑:crontab
。我们将从查看crontab
命令和语法开始。
Linux 系统上的每个用户都可以拥有自己的 crontab。还有一个系统范围的 crontab(不要和可以在 root 用户下运行的 crontab 混淆!),用于周期性任务;我们将在本章后面讨论这些问题。现在,我们将从探索 crontab 语法开始,并为我们的读者用户创建第一个 crontab。
虽然语法最初看起来很混乱,但实际上并不难理解,而且非常灵活:
command
哇,那很简单!如果真的是这样,那么是的。然而,我们上面描述的实际上是由五个不同的字段组成的,它们构成了多次运行作业的组合周期。实际上,时间戳定义如下(按顺序):
- 一分钟一小时
- 每日一小时
- 每月的某一天
- 月
- 一周中的某一天
在这些值中的任何一个中,我们可以用一个数字代替一个通配符,表示所有的值。查看下表,了解我们如何将这五个字段组合在一起以获得精确的时间:
| 克朗塔布语法 | 语义 | | 15 16 * * * | 每天 16:15。 | | 30 * * * * | 每小时一次,在 xx:30(因为每个小时由于通配符而有效)。 | | * 20 * * * | 每天 60 次,在 20:00 到 20:59 之间(小时是固定的,分钟有通配符)。 | | 10 10 1 * * | 每个月的第一天,10:10。 | | 00 21 * * 1 | 每周一次,周一 21:00(1-7 是周一到周日,周日也是 0)。 | | 59 23 31 12 * | 就在新年前,12 月 31 日 23:59。 | | 01 00 1 1 3 | 1 月 1 日 00:01,但前提是发生在周三(这将发生在 2020 年)。 |
你可能会对这个语法有点困惑。由于我们中的许多人通常将时间写为 18:30,因此颠倒分钟和小时似乎有点违背直觉。然而,事情就是这样(相信我们,你很快就会习惯 crontab 格式)。现在,也有一些高级技巧可以使用这种语法:
- 8-16(连字符允许多个值,因此
00 8-16 * * *
表示从 08:00 到 16:00 的每一个完整小时)。 - /5 允许每 5 个单位(最常用于第一个位置,每 5 分钟)。小时的值/6 也很有用,一天四次。
- 00,30 表示两个值,例如每小时 30 分钟或半小时(也可以写成*/30)。
在我们陷入理论之前,让我们使用crontab
命令为用户创建一个简单的第一个 crontab。crontab
命令有三个你最常使用的有趣标志:-l
代表列表,-e
代表编辑,-r
代表移除。让我们使用这三个命令创建(并删除)第一个 crontab:
reader@ubuntu:~$ crontab -l
no crontab for reader
reader@ubuntu:~$ crontab -e
no crontab for reader - using an empty one
Select an editor. To change later, run 'select-editor'.
1\. /bin/nano <---- easiest
2\. /usr/bin/vim.basic
3\. /usr/bin/vim.tiny
4\. /bin/ed
Choose 1-4 [1]: 2
crontab: installing new crontab
reader@ubuntu:~$ crontab -l
# m h dom mon dow command
* * * * * wall "Crontab rules!"
Broadcast message from reader@ubuntu (somewhere) (Sun Nov 25 16:25:01 2018):
Crontab rules!
reader@ubuntu:~$ crontab -r
reader@ubuntu:~$ crontab -l
no crontab for reader
如您所见,我们首先使用crontab -l
命令列出当前的 crontab。因为我们没有一个,所以我们看到消息没有 crontab 为读者(没有惊喜)。接下来,当我们使用crontab -e
开始编辑 crontab 时,我们会得到一个选择:我们要使用哪个编辑器?一如既往,做对你最有利的事。我们对vim
有足够的经验,比起nano
我们更喜欢它。我们只需要为每个用户做一次,因为 Linux 会保存我们的偏好(查看~/)。selected_editor 文件)。最后,我们将看到一个文本编辑器屏幕,在我们的 Ubuntu 机器上,它充满了关于 crontabs 的小教程。由于所有这些行都以#开头,因此都被视为注释,不会干扰执行。通常,除了语法提示:m . h . DOM mon Dow 命令之外,我们删除一切*。您可能会忘记这个语法几次,这就是为什么当您需要快速编辑时,这个小提示会有很大帮助,尤其是当您已经有一段时间没有使用 crontab 了。*
我们用最简单的时间语法创建一个 crontab:在所有五个位置使用通配符。简单的说,就是指定后的命令是每分钟运行*。保存并退出后,我们最多等待一分钟,然后才能看到wall "Crontab rules!";
命令的结果,这是我们自己用户的广播,系统上的所有用户都可以看到。因为这种结构严重破坏了系统,我们使用crontab -r
在一次广播后删除了 crontab。或者,我们也可以删除那一行或者注释掉它。*
*A crontab can have many entries. Each entry has to be placed on its own line, with its own time syntax. This allows for a user to have many different jobs scheduled, at different frequencies. Because of this, crontab -r
is not often used, and by itself is pretty destructive. We would advise you to always use crontab -e
to ensure you do not accidentally delete your whole job schedule, but just the bits that you want.
如上所述,所有 crontabs 都保存为文件系统中的文件。您可以在/var/spool/cron/cron tab/目录中找到它们。该目录只能由根用户访问;如果所有用户都能看到彼此的工作时间表,这将会有一些重大的隐私问题。但是,如果您使用sudo
成为根,您将看到以下内容:
reader@ubuntu:~$ sudo -i
[sudo] password for reader:
root@ubuntu:~# cd /var/spool/cron/crontabs/
root@ubuntu:/var/spool/cron/crontabs# ls -l
total 4
-rw------- 1 reader crontab 1090 Nov 25 16:51 reader
如果我们打开这个文件(vim
、less
、cat
,无论你喜欢什么),我们会看到与读者用户向我们展示的crontab -e
相同的内容。不过,一般来说,总是使用可用的工具来编辑像这样的文件!这样做的主要附加好处是,这些工具不允许您保存不正确的格式。如果我们手工编辑 crontab 文件,并且时间语法错误,整个 crontab 将不再工作。如果您对crontab -e
执行同样的操作,您将看到一个错误,crontab 将不会被保存,如下所示:
reader@ubuntu:~$ crontab -e
crontab: installing new crontab
"/tmp/crontab.ABXIt7/crontab":23: bad day-of-week
errors in crontab file, can't install.
Do you want to retry the same edit? (y/n)
在前面的例子中,我们输入了行* * * * true
。从错误中可以看出,cron 需要一个数字或通配符,它会找到命令true
(您可能还记得,这是一个只返回退出代码 0 的命令)。它向用户显示一个错误,并拒绝保存新的编辑,这意味着所有以前计划的作业都是安全的,并将继续运行,即使我们这次搞砸了。
The time syntax for crontab allows pretty much any combination you could think of. However, sometimes you do not really care about an exact time, but are more interested in making sure something runs hourly, daily, weekly, or even monthly. Cron has some special time syntaxes for this: instead of the five values you normally insert, you can tell the crontab @hourly
, @daily
, @weekly
, and @monthly
.
按计划运行脚本是自动化重复性任务的一个好方法。但是,在这样做的时候有一个很大的考虑:日志。通常,当您运行一个命令时,输出将对您直接可见。如果出现问题,你就在键盘后面调查问题。然而,一旦我们开始使用cron
(甚至at
),我们就不再看到命令的直接输出。我们一登录就只能查结果,如果不做安排,只能找脚本的结果(比如清理日志文件)。我们需要的是记录我们的脚本,所以我们有一个简单的方法来定期验证我们的脚本是否成功运行。
在我们的 crontab 中,我们可以定义环境变量,这些变量将被我们的命令和脚本使用。crontab 的这个函数使用非常频繁,但大部分只用于三个环境变量:PATH、SHELL 和 MAILTO。我们将看看这些变量的用例/必要性。
通常,当你登录一个 Linux 系统时,你会得到一个登录 Shell。登录 Shell 是一个完全交互式的 Shell,它为您做了一些很酷的事情:它设置 PS1 变量(它决定了您的提示的外观),正确设置您的路径,等等。现在,正如您可能想象的那样,除了登录 Shell 之外,还有其他东西。从技术上讲,有两个维度构成了四种不同的壳:
| | 登录 | 不登录 | | 互动 | 交互式登录 Shell | 交互式非登录 Shell | | 非互动 | 非交互式登录 Shell | 非交互式非登录 Shell |
大多数情况下,您会使用交互式登录 Shell,例如当您通过(SSH)或直接通过终端控制台连接时。另一个经常遇到的 shell 是非交互非登录 shell ,这是通过at
或cron
运行命令时使用的。另外两个是可能的,但是我们不会详细讨论你什么时候能得到它们。
那么,既然您知道我们在at
和cron
中获得了不同类型的 Shell,我们肯定您想知道它们的区别是什么(如中所示,您为什么关心这个?).有许多文件可以在 Bash 中设置您的配置文件。这里列出了其中的一些:
/etc/profile
/etc/bash.bashrc
~/.profile
~/.bashrc
位于/etc/中的前两个是系统范围的文件,因此对所有用户都是相同的。后两个在你的主目录中,是个人的;这些可以编辑,例如,添加您想要使用的别名。alias
命令用于创建带有标志的命令的简写。~/。默认情况下,在 Ubuntu 18.04 上,bashrc 文件包含行alias ll='ls -alF'
,这意味着您可以键入ll
并执行ls -alF
。
在不涉及太多细节的情况下(过于简单),交互登录 Shell 会读取并解析所有这些文件,而非交互非登录 Shell 则不会(更多详细信息,请参见进一步阅读部分)。一如既往,一张图片胜过千言万语,所以让我们自己来看看它们的不同之处:
reader@ubuntu:~$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
reader@ubuntu:~$ echo $PS1
\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$
reader@ubuntu:~$ echo $0
-bash
reader@ubuntu:~$ at now
warning: commands will be executed using /bin/sh
at> echo $PATH
at> echo $PS1
at> echo $0
at> <EOT>
job 19 at Sat Dec 1 10:36:00 2018
You have mail in /var/mail/reader
reader@ubuntu:~$ tail -5 /var/mail/reader
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
$
sh
正如我们在这里看到的,普通(SSH)Shell 和at
执行的命令之间的值是不同的。这适用于 PS1 和贝壳本身(我们可以用 0 美元找到)。然而,对于at
,路径与交互式登录会话相同。现在,看看如果我们在 crontab 中这样做会发生什么:
reader@ubuntu:~$ crontab -e
crontab: installing new crontab
reader@ubuntu:~$ crontab -l
# m h dom mon dow command
* * * * * echo $PATH; echo $PS1; echo $0
You have mail in /var/mail/reader
reader@ubuntu:~$ tail -4 /var/mail/reader
/usr/bin:/bin
$
/bin/sh
reader@ubuntu:~$ crontab -r # So we don't keep doing this every minute!
开始,PS1 等于at
看到的。由于 PS1 控制着 Shell 的外观,这仅在交互式会话中才有意思;at
和cron
都是非交互式的。如果我们继续到 PATH ,我们会看到一个截然不同的故事:在cron
中运行时,我们得到的是/usr/bin:/bin 而不是/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin!简单地说,这意味着对于/bin/和/usr/bin/之外的所有命令,我们需要使用完全限定的文件名。这甚至体现在$0 的差异上(sh 对/bin/sh)。虽然这并不是绝对必要的(因为/bin/实际上是 PATH 的一部分),但是仍然可以看到与cron
相关的完全合格的 PATH。
现在,我们有两个选项来处理这个问题,如果我们想防止像 sudo: command not found 这样的错误。我们可以确保总是对所有命令使用完全限定的路径(实际上,这肯定会失败几次),也可以确保为 crontab 设置一个路径。第一个选项给了我们更多额外的工作来完成我们将要做的所有事情。第二个选择实际上是确保我们否定这个问题的一个非常简单的方法。我们可以简单地在 crontab 的顶部包含一个PATH=...
,crontab 执行的所有事情都使用这个路径。尝试以下方法:
reader@ubuntu:~$ crontab -e
no crontab for reader - using an empty one
crontab: installing new crontab
reader@ubuntu:~$ crontab -l
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
# m h dom mon dow command
* * * * * echo $PATH
reader@ubuntu:~$
You have new mail in /var/mail/reader
reader@ubuntu:~$ crontab -r
reader@ubuntu:~$ tail -2 /var/mail/reader
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
很简单。如果您想自己验证这一点,您可以保留默认路径,并从/sbin/(例如显示磁盘/分区信息的blkid
命令)运行一些东西。由于它不在 PATH 上,如果您没有完全合格地运行它,您将会遇到错误/bin/sh: 1: blkid:在您的本地邮件中找不到。选择任何你可以正常运行的命令,并尝试它!
With this simple addition to a crontab, you can save yourself a lot of time and effort troubleshooting errors. As with all things in scheduling, you often have to wait at least a few minutes for each script attempt to run, making troubleshooting a time-intensive practice. Do yourself a favor and always make sure to include a relevant PATH as the first line of your crontab.
从我们看到的路径的输出应该很清楚,默认情况下at
和cron
都使用/bin/sh。您可能会很幸运,并且有一个/bin/sh 默认为 Bash 的发行版,但这不一定是这样的,尤其是如果您跟随我们的 Ubuntu 18.04 安装的话!在这种情况下,如果我们签出/bin/sh,我们会看到完全不同的东西:
reader@ubuntu:~$ ls -l /bin/sh
lrwxrwxrwx 1 root root 4 Apr 26 2018 /bin/sh -> dash
Dash 是T3】DebianAlmquistshell,这是最近的 Debian 系统上的默认系统 Shell(你可能还记得,Ubuntu 属于 Debian 发行家族)。虽然 Dash 是一个很棒的 Shell,有自己的一套优点和缺点,但这本书是为 Bash 写的。因此,对于我们的用例来说,让cron
默认使用 Dash shell 是不切实际的,因为这不允许我们使用很酷的 Bash 4.x 函数,比如高级重定向、某些扩展等等。幸运的是,我们可以很容易地设置cron
在运行命令时应该使用的 shell:我们使用 SHELL 环境变量。设置这个真的很简单:
reader@ubuntu:~$ crontab -e
crontab: installing new crontab
reader@ubuntu:~$ crontab -l
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
# m h dom mon dow command
* * * * * echo $0
reader@ubuntu:~$
You have mail in /var/mail/reader
reader@ubuntu:~$ tail -3 /var/mail/reader
/bin/bash
reader@ubuntu:~/scripts/chapter_14$ crontab -r
只需简单地添加 SHELL 环境变量,我们就能确保不会有令人难以置信的问题,即为什么某些 Bash 功能不起作用。防止这些问题总是一个好主意,而不是希望你能很快发现它们,尤其是如果你还在掌握 shell 脚本的话。
现在我们已经确定可以通过检查 PATH 和 SHELL 来使用 crontab 中的环境变量,让我们看看另一个非常重要的变量,MAILTO。正如你可能从名字中猜到的,这个变量控制邮件将被发送到哪里。正如你所记得的,当一个命令有 stdout(几乎都是命令)时,就会发送邮件。这意味着,对于 crontab 执行的每个命令,您可能会收到一封本地电子邮件。你可能会怀疑,这会很快变得令人讨厌。我们可以在 crontab 中放置的所有命令后面加上一个小后缀&> /dev/null
(记住,&>
是 Bash 特有的,对于默认的 Dash shell 是不起作用的)。然而,这将意味着我们从来没有任何输出,邮寄或其他方式。除了这个问题,我们还需要将它添加到所有的行中;并不是真正实用可行的解决方案。在接下来的几页中,我们将讨论如何将输出重定向到我们想要的地方。然而,在此之前,我们还需要能够操作默认的电子邮件。
一种选择是不安装或卸载sendmail
。对于你们中的一些人来说,这可能是一个很好的解决方案,但是对于其他人来说,系统上还需要有sendmail
,所以它不能被移除。然后呢?我们可以像使用路径一样使用 MAILTO 变量;我们在 crontab 的开头设置它,邮件将被正确重定向。如果我们清空这个变量,通过给它赋值空字符串""
,就不会发送邮件。这看起来像这样:
reader@ubuntu:~$ crontab -e
no crontab for reader - using an empty one
crontab: installing new crontab
reader@ubuntu:~$ crontab -l
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
MAILTO=""
# m h dom mon dow command
* * * * * echo "So, I guess we'll never see this :("
到目前为止,我们已经使用了很多tail
命令,但是它实际上有一个很好的小标志--follow
( -f
),允许我们查看是否有新的行被写入文件。这通常用于跟踪日志文件,但在这种情况下,我们可以通过跟踪/var/mail/reader 文件来查看是否收到邮件:
reader@ubuntu:~$ tail -f /var/mail/reader
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Cron-Env: <SHELL=/bin/sh>
X-Cron-Env: <HOME=/home/reader>
X-Cron-Env: <PATH=/usr/bin:/bin>
X-Cron-Env: <LOGNAME=reader>
/bin/bash: 1: blkid: not found
如果一切如我们所料,这是你唯一能看到的。由于 MAILTO 变量被声明为空字符串,""
,cron
知道不发送邮件。用 Ctrl + C 退出tail -f
(但记住命令),现在你已经防止自己被你的 crontab 垃圾邮件了,请放心!
虽然垃圾邮件已经被消除,但现在你发现自己根本没有任何输出,这也绝对不是一件好事。幸运的是,我们已经在 第 12 章中学习了所有关于重定向的知识,在脚本中使用管道和重定向 。正如我们可以在命令行上的脚本或中使用重定向一样,我们也可以在 crontab 中使用相同的构造。管道和 stdout/stderr 的排序规则相同,因此我们可以链接任何我们想要的命令。然而,在展示这个之前,我们将展示 crontab 的一个更酷的功能:从一个文件实例化一个 crontab!**
reader@ubuntu:~/scripts/chapter_14$ vim base-crontab
reader@ubuntu:~/scripts/chapter_14$ cat base-crontab
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
MAILTO=""
# m h dom mon dow command
reader@ubuntu:~/scripts/chapter_14$ crontab base-crontab
reader@ubuntu:~/scripts/chapter_14$ crontab -l
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
MAILTO=""
# m h dom mon dow command
首先,我们创建基本的 crontab 文件,它包含我们的 Bash SHELL、PATH(我们稍微修剪了一下)、MAILTO 变量和我们的语法头。接下来,我们使用crontab base-crontab
命令。简单地说,这用文件中的内容替换了当前的 crontab。这意味着我们现在可以将 crontab 作为文件进行管理;这包括对版本控制系统和其他备份解决方案的支持。更好的是,当使用crontab <filename>
命令时,语法检查是完整的。如果文件不是正确的 crontab 格式,您会在 crontab 文件中看到错误错误,无法安装。如果您希望将当前的 crontab 保存到一个文件中,crontab -l > filename
命令将为您完成该操作。
既然已经排除了这种情况,我们将给出一些由 crontab 运行的命令的重定向示例。我们总是从一个文件中实例化,因此您可以在 GitHub 页面上轻松找到这些资料:
reader@ubuntu:~/scripts/chapter_14$ cp base-crontab date-redirection-crontab
reader@ubuntu:~/scripts/chapter_14$ vim date-redirection-crontab
reader@ubuntu:~/scripts/chapter_14$ cat date-redirection-crontab
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
MAILTO=""
# m h dom mon dow command
* * * * * date &>> /tmp/date-file
reader@ubuntu:~/scripts/chapter_14$ crontab date-redirection-crontab
reader@ubuntu:~/scripts/chapter_14$ tail -f /tmp/date-file
Sat Dec 1 15:01:01 UTC 2018
Sat Dec 1 15:02:01 UTC 2018
Sat Dec 1 15:03:01 UTC 2018
^C
reader@ubuntu:~/scripts/chapter_14$ crontab -r
这很简单。只要我们的 SHELL、PATH **、**和 MAILTO 设置正确,我们就避免了很多通常情况下人们通过 crontab 开始处理日程安排时会遇到的问题。
我们还没有做的一件事是用 crontab 运行一个脚本。到目前为止,只运行了单个命令。然而,一个脚本会运行得一样好。我们将使用上一章中的脚本,反向器. sh,它将显示我们也可以通过 crontab 向脚本提供参数。此外,它将显示我们刚刚学习的重定向同样适用于脚本输出:
reader@ubuntu:~/scripts/chapter_14$ cp base-crontab reverser-crontab
reader@ubuntu:~/scripts/chapter_14$ vim reverser-crontab
reader@ubuntu:~/scripts/chapter_14$ cat reverser-crontab
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
MAILTO=""
# m h dom mon dow command
* * * * * /home/reader/scripts/chapter_13/reverser.sh 'crontab' &>> /tmp/reverser.log
reader@ubuntu:~/scripts/chapter_14$ crontab reverser-crontab
reader@ubuntu:~/scripts/chapter_14$ cat /tmp/reverser.log
/bin/bash: /home/reader/scripts/chapter_13/reverser.sh: Permission denied
reader@ubuntu:~/scripts/chapter_14$ crontab -r
哎哟!经过我们所有的精心准备,我们还是在这里搞砸了。幸运的是,我们创建的输出文件(用作日志文件,并具有。日志扩展,因为它)也有 stderr 重定向(因为我们的 Bash 4.x &>>
语法),我们看到错误是什么。一个典型的错误,权限被拒绝在这种情况下仅仅意味着我们试图执行一个不可执行的文件:
reader@ubuntu:~/scripts/chapter_14$ ls -l /home/reader/scripts/chapter_13/reverser.sh
-rw-rw-r-- 1 reader reader 933 Nov 17 15:18 /home/reader/scripts/chapter_13/reverser.sh
所以,我们需要解决这个问题。我们可以做两件事:
- 用(例如)
chmod 755 reverser.sh
使文件可执行。 - 将 crontab 从
reverser.sh
更改为bash reverser.sh
。
在这种情况下,并没有真正的好或坏的解决方案。一方面,将需要执行的文件标记为可执行文件总是一个好主意;这向看到系统的人传达了你的意图。另一方面,如果 crontab 中额外的bash
命令可以将您从这些类型的问题中解救出来,这有什么坏处呢?
在我们看来,让文件可执行并省略 crontab 中的bash
命令会有更多的好处。这使得 crontab 更加干净(并且,根据经验,如果处理不当,crontab 很容易变得一团糟,所以这是一个非常大的优势),并且向查看脚本的其他人显示,由于权限的原因,它应该被执行。让我们将此修复应用于我们的机器:
reader@ubuntu:~/scripts/chapter_14$ chmod 755 ../chapter_13/reverser.sh
reader@ubuntu:~/scripts/chapter_14$ crontab reverser-crontab
reader@ubuntu:~/scripts/chapter_14$ tail -f /tmp/reverser.log
/bin/bash: /home/reader/scripts/chapter_13/reverser.sh: Permission denied
Your reversed input is: _batnorc_
^C
reader@ubuntu:~/scripts/chapter_14$ crontab -r
好了,好多了。我们在 crontab 中运行的完整命令是/home/reader/scripts/chapter_13/reverser.sh 'crontab' &>> /tmp/reverser.log
,它包括作为脚本第一个参数的单词 crontab。输出 batnorc 确实是相反的词。似乎我们可以通过 crontab 正确地传递参数!虽然这个例子说明了这一点,但它可能不会被理解,尽管这可能很重要。然而,如果您想象一个普通的脚本通常使用不同的参数多次,那么它也可以在 crontab 中与这些不同的参数一起出现(在多行上,可能有不同的计划)。确实很有用!
If you ever need to quickly look up what the deal with the crontab was, you would of course check out man crontab
. However, what we haven't told you yet is that some commands actually have more than one man page! By default, man crontab
is shorthand for man <first-manpage> crontab
. On that page, you'll see the sentence, "SEE ALSO crontab(5), cron(8)". By supplying this number with man 5 crontab
, you'll see a different page where many of the concepts of this chapter (syntax, environment variables, and examples) are easily accessible to you.
您可以考虑让您的脚本处理自己的日志记录。虽然这当然是可能的(虽然有点复杂,不太可读),但我们强烈认为是调用者负责记录的责任。如果您发现一个处理自己的日志记录的脚本,您可能会遇到以下一些问题:
- 多个用户以不同的时间间隔运行同一个脚本,生成一个日志文件
- 日志文件需要具有强大的用户权限,以确保正确的暴露
- 临时运行和计划运行都将出现在日志文件中
简单地说,将日志记录的责任委托给脚本本身就是自找麻烦。对于特定命令,您可以在终端中获得正确的输出。如果你需要它做其他用途,你可以复制并粘贴到某个地方,或者重定向它。更有可能的是用管道将脚本运行到tee
,因此输出会显示到您的终端并同时保存到一个文件中。对于从cron
开始的计划运行,您需要考虑一下重定向:当您创建计划时。在这种情况下,尤其是如果您使用&>>
的 Bash 4.x 构造,您将总是看到所有输出(stdout 和 stderr)被附加到您指定的文件。在这种情况下,几乎没有遗漏任何输出的风险。请记住:tee
和重定向是你的朋友,如果使用得当,它们是任何脚本调度的一个很好的补充!
If you want your cron logging mechanism to be really fancy, you can set up sendmail
(or other software such as postfix
) as an actual Mail Transfer Agent (very out of the scope of this book, but check the Further reading section!). If that is correctly configured, you can set the MAILTO variable in the crontab to an actual email address (perhaps [email protected]
), and receive the reports from scheduled jobs in your normal email box. This is best used with important scripts that do not run too often; otherwise, you will just end up with an annoying amount of email.
重要的是要认识到,正如它直接在命令行上一样,只记录输出(stdout/stderr)。默认情况下,大多数成功运行的命令没有任何输出;例如cp
、rm
、touch
等等。如果您想在脚本中记录信息,您有责任在您认为合适的地方添加输出。最简单的方法就是到处使用echo
。让日志文件给用户信心最简单的方法是让脚本中的最后一个命令成为echo "Everything went well, exiting script."
。只要您在脚本中正确处理所有潜在的错误,您就可以放心地说,一旦它到达最终命令,执行就成功了,并且您可以通知用户这一点。如果不这样做,日志文件可能会保持为空,这有点可怕;是因为一切都成功了还是因为剧本连都没运行就空了?这不是你想冒险的事情,尤其是当一个简单的echo
就能帮你省去所有麻烦的时候。
本章首先展示了新的at
命令,并解释了如何使用at
来调度脚本。我们描述了at
的时间戳语法,以及它如何包含所有计划作业的队列。在我们继续更强大的cron
调度器之前,我们解释了at
如何主要用于临时调度的命令和脚本。
cron
守护程序负责系统上的大多数计划任务,是一个非常强大和灵活的调度程序,最常通过所谓的 crontab 使用。这是一个用户绑定文件,其中包含cron
何时以及如何运行命令和脚本的说明。我们展示了 crontab 中使用的时间戳语法。
这一章的第二部分涉及记录我们计划的命令和脚本。当命令在命令行上以交互方式运行时,不需要专门的日志记录,但是计划的命令不是交互的,因此需要额外的机制。调度命令的输出可以通过sendmail
过程邮寄到本地文件,或者使用我们前面概述的重定向可能性重定向到日志文件。
我们用一些关于日志记录的最后考虑来结束这一章:如何总是由调用者负责安排日志记录,以及如何由脚本作者负责确保脚本足够详细,可以非交互地使用。
本章介绍了以下命令:at
、wall
、atq
、atrm
、sendmail
、crontab
和alias
。
- 什么是排班?
- 我们所说的临时安排是什么意思?
at
正常运行的命令输出去哪里了?cron
守护进程的调度通常是如何实现的?- 哪些命令允许您编辑个人 crontab?
- crontab 时间戳语法中有哪五个字段?
- crontab 最重要的三个环境变量是什么?
- 如何检查我们用
cron
安排的脚本或命令的输出? - 如果我们的计划脚本没有足够的输出来有效地处理日志文件,我们应该如何补救?
如果您想深入了解本章的主题,以下资源可能会很有意思:
- Profile and Bashrc:https://bencane . com/2013/09/16/了解-多一点-etcprofile-and-etcbashrc/
- 用后缀设置邮件传输代理:https://www.hiroom2.com/2018/05/06/ubuntu-1804-postfix-en/T4*