今天早上我发现一条消息,内容是这样的:
编辑:我还添加了消息标题,这样就可以清楚知道它来自哪里。
Return-Path: <root@REDACTED>
Received: from localhost (localhost [127.0.0.1])
(uid 0)
by REDACTED with local
id 00000000005DC0DF.00000000633BA87E.000042C7; Tue, 04 Oct 2022 05:29:02 +0200
From: CronDaemon <root@REDACTED>
To: admlog@REDACTED
Subject: Cron <root@north> test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
Mime-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit
X-Cron-Env: <SHELL=/bin/sh>
X-Cron-Env: <PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin>
X-Cron-Env: <MAILTO=REDACTED>
X-Cron-Env: <HOME=/root>
X-Cron-Env: <LOGNAME=root>
Message-ID: <courier.00000000633BA87E.000042C7@REDACTED>
Date: Tue, 04 Oct 2022 05:29:02 +0200
X-Mime-Autoconverted: from 8bit to 7bit by courier 1.1
/etc/cron.daily/syslogrotate:
parse error: Invalid numeric literal at line 2, column 0
parse error: Invalid numeric literal at line 2, column 0
我修改syslogrotate
为调用一个 shell 脚本来查看旋转的文件,并根据需要调用其他脚本。 Cron 将写入 stderr 的任何内容归因于它启动的第一个进程。如果知道实际的脚本文件,甚至行号,那就太好了。难道没有一个工具可以做到这一点吗?
答案1
让 syslog 调用的脚本检查它调用的每个子任务的退出代码,当子任务以非零代码退出时,脚本会向 stderr 写入一条消息,说明脚本的路径和名称。例如,/path/to/the/sub_task.sh returned exit code 2
。该消息将包含在来自 cron 的电子邮件中,您将获得所需的脚本名称。
答案2
不,没有这样的工具。
但bash
有一些工具可以找到错误。
#!/bin/bash -v
您的脚本将在执行之前打印每个命令。这将显示错误到底发生在哪里。
或者您可以使用-x
的密钥bash
。这将提供有关脚本执行的更多信息。
如果您将错误定位到脚本的一小部分并且不想再打印全部内容,您可以使用set -xv
:
#!/bin/bash
some-good-commands
set -xv # debug mode on
some-suspicious-code
set +xv # debug mode off
some-good-commands
如果手动运行脚本无法让您了解问题的根源,则可以直接在 cron 作业中使用这些内容。
当然,您始终可以添加调试echo
:
#!/bin/bash
echo "Starting script A"
VAR=$1
echo "Executing `abc $VAR`"
abc $VAR
echo "abc ended with $?"
答案3
Sotto Voce 的想法很好。我们可以测试是否写出了任何内容,而不是测试返回代码。这样一来,人们就可以检测 cron 脚本来编写一些上下文,以防出现错误。
我不知道如何在 bash 中测试输出,所以我编写了一个名为的小 C 实用程序any_output
:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
long cur_out = lseek(1, 0, SEEK_END);
if (cur_out < 0) cur_out = 0;
long cur_err = lseek(2, 0, SEEK_END);
if (cur_err < 0) cur_err = 0;
if (argc > 1)
{
char *slash, *end;
long last_out = strtol(argv[1], &slash, 10);
if (*slash == '/')
{
long last_err = strtol(slash + 1, &end, 10);
if (*end != 0 && *end != '\n')
last_err = -1;
else
{
int rtc = cur_err > last_err || cur_out > last_out;
if (rtc)
fprintf(stderr, "%s, %ld > %ld || %ld > %ld\n",
argv[1], cur_err, last_err, cur_out, last_out);
return rtc;
}
}
else
{
fputs("argument to any_output is its previous output\n", stderr);
return 0;
}
}
else
{
printf("%ld/%ld\n", cur_out, cur_err);
return 0;
}
}
接下来我写了一个测试脚本。它调用了,jq
因为这是我得到的错误。
#! /bin/bash
output=$(/home/ale/tmp/any_output)
if [ -f /home/ale/tmp/test.data ]; then
if [[ "$(jq .j < /home/ale/tmp/test.data)" != "1" ]]; then
touch /home/ale/tmp/test.data
fi
fi
/home/ale/tmp/any_output $output || echo ${BASH_SOURCE[*]}
该路径是我当前目录的路径。 test.data 包含在内{"j":1}
,因此该脚本几乎是无操作的。我设置了 crontab 每两分钟调用一次测试脚本。有一段时间没有发生任何事情,直到我写入hello
test.data。在下次运行时,cron 给我发了这样的邮件:
From: CronDaemon <REDACTED>
To: REDACTED
Subject: Cron <ale@pcale> /home/ale/tmp/test.sh
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit
X-Cron-Env: <SHELL=/bin/sh>
X-Cron-Env: <PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin>
X-Cron-Env: <MAILTO=REDACTED>
X-Cron-Env: <HOME=/home/ale>
X-Cron-Env: <LOGNAME=ale>
Date: Thu, 06 Oct 2022 14:18:01 +0200
X-Mime-Autoconverted: from 8bit to 7bit by courier 1.0
parse error: Invalid numeric literal at line 2, column 0
0/0, 57 > 0 || 57 > 0
/home/ale/tmp/test.sh
正文第二行中的乱码用于调试仪器本身。我有一些误报,因为lseek(1, 0, SEEK_END)
返回 -1,即使isatty(1)
是 false。所以我删除了对上面的调用isatty
并设置了代码。