我只想stdout
从 重定向到日志文件crontab
,并让 crontab 通过邮件通知我出现错误。
如果我想重定向 std 和 err 流,我会选择2>&1
:
[email protected]
0 23 * * * /home/john/import.sh > /home/john/logs/backup.log 2>&1
但这不会阻止 crontab 捕获错误并发送邮件通知吗?这就是为什么我正在寻找一种仅重定向标准输出的方法。
我的问题是我的import.sh
脚本运行 mysql 导入。奇怪的是 crontab 正在向我发送有关它的电子邮件,即使没有错误:
mysql: [Warning] Using a password on the command line interface can be insecure.
Importing from file '/tmp/my.csv' to table `mytable` in MySQL Server at /var%2Frun%2Fmysqld%2Fmysqld.sock using 3 threads
[Worker001] Records: 340086 Deleted: 0 Skipped: 0 Warnings: 0
[Worker002] Records: 351334
...
File '/tmp/my.csv' was imported in 1 min 9.9572 sec at 59.91 MB/s
因此,我正在寻找一种方法来仍然记录这些语句,但仅在错误情况下通知我。
更新:将 crontab 镜像如下:
*/1 * * * * /opt/test.sh
测试.sh:
#!/usr/bin/env bash
echo "just testing"
exit 0
结果:crontab 将总是发送电子邮件通知,即使它只是带有成功代码的标准输出。但为什么?我怎样才能防止这种情况发生?
答案1
您可以尝试如下操作:
0 23 * * * :> /home/john/logs/backup.log && /home/john/import.sh 2> >(tee -a /home/john/logs/backup.log) >> /home/john/logs/backup.log
stderr
将同时访问stderr
AND 文件(通过命令tee
),而stdout
只会访问文件。
我必须附加(命令-a
的tee
和>>
标准输出的到文件)以确保tee
和重定向不会相互覆盖。
我:> /home/john/logs/backup.log
一开始就在每次 cron 运行之前截断日志,因为由于上述原因我们要附加到文件中。
更新
我不喜欢在我之前的答案中,有两个进程附加到同一个文件,这迫使我首先截断它。我找到了另一个解决方案:
0 23 * * * (( /home/john/import.sh 2> >(tee /proc/self/fd/3) 1>&3 3>&- ) 3>&1 1>&2) |cat > /home/john/logs/backup.log
它仅适用于bash
,不适用于 POSIX sh
,因此您需要使用 来更改 crontab 文件中的 shell SHELL=/bin/bash
。
这是尝试解释我在这里所做的事情:
((...) 3>&1 1>&2)
这将创建一个新的文件描述符3这将重定向到 fd 1 所保存的内容 - 这是通往 的管道cat
。 fd 1 将转至 fd 2 所保存的内容 - 即终端(或者在您的情况下,是 cron 的输入)。
/home/john/import.sh 2> >(tee ... ) 1>&3 3>&-
该脚本会将其 stdout 定向到 fd 3 所保存的内容(即指向cat
. 的管道),并将 stderr 定向到 tee。
它将关闭 fd 3,因为不再需要它 - 脚本的标准输出已经转到 3 所持有的内容。
tee /proc/self/fd/3
tee 将把两者写入 fd 3 所保存的内容(同样,管道到cat
)和到终端(或者在你的情况下,作为 cron 的输入)。
(...) |cat > /home/john/logs/backup.log
cat
将从其标准输入读取流,并将写入文件backup.log
。
结果:
- 该脚本会将其标准输出写入 的标准输入
cat
。 - 该脚本还将其 stderr 写入
tee
.tee
会将其输入(脚本的 stderr)写入:- 终点站
- 的标准输入
cat
。
cat
从以下位置获取输入:- 脚本的标准输出
- 脚本的 stderr(通过
tee
)
cat
会将 stdin 中得到的内容写入文件。- 唯一真正返回到 cron 的是脚本的 stderr(同样,通过
tee
)。
答案2
定时任务
[email protected]
0 23 * * * /home/john/import.sh
导入.sh
#!/bin/bash
exec 3>&1 # Save a reference to current STDOUT as file descriptor 3
# Redirect stdout and stderr to the log file
exec >>"/home/john/logs/backup.log" 2>&1
echo "$(date +%Y%m%dT%H%M) info: job started"
output_from_mysql_import_operation=$(
mysql <data-to-import.sql 2>&1
)
mysql_exit_status=$?
if [[ $mysql_exit_status != 0 ]]; then
# This is sent to file descriptor 3, which is the original stdout
# Thus, cron will collect this and send an email
cat <<EOF >&3
cron job failed for script $0 at $(date +%Y%m%dT%H%M)
An error occurred during the import operation.
The mysql exit status code was $mysql_exit_status.
The output from mysql follows.
$output_from_mysql_import_operation
EOF
# This will end up in the log file
cat <<EOF
$(date +%Y%m%dT%H%M): error: mysql exited with a non-zero exit status"
The mysql exit status code was $mysql_exit_status.
The output from mysql follows.
$output_from_mysql_import_operation
Exiting script
EOF
exit 1
fi
echo "$output_from_mysql_import_operation"
echo
echo "$(date +%Y%m%dT%H%M) info: job completed successfully"
答案3
import.sh 文件实际上有什么作用?假设它正在运行类似的命令mysql
,它应该返回一个退出代码根据执行结果。然后检查退出代码。
尝试:
echo $?
并检查失败的语句是否返回非零值。
因此,我建议您在脚本中进行错误处理,并在出现错误时通过邮件发送日志。将 stdout 和 stderr 记录到文件中,但不要依赖 cron 来过滤掉 stderr。
答案4
*/1 * * * * /opt/test.sh 1>tmp.log
样品测试:
@Station:~$ ls /var 1>/dev/null
@Station:~$ ls /varr 1>/dev/null
ls: cannot access '/varr': No such file or directory
@Station:~$ # splitting args:
@Station:~$ ls 1>/dev/null -- /var
@Station:~$ ls 1>/dev/null -- /varr
ls: cannot access '/varr': No such file or directory
@Station:~$