如何在 crontab 中仅重定向标准输出?

如何在 crontab 中仅重定向标准输出?

我只想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将同时访问stderrAND 文件(通过命令tee),而stdout只会访问文件。

我必须附加(命令-atee>>标准输出的到文件)以确保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

这是尝试解释我在这里所做的事情:

  1. ((...) 3>&1 1>&2)

这将创建一个新的文件描述符3这将重定向到 fd 1 所保存的内容 - 这是通往 的管道cat。 fd 1 将转至 fd 2 所保存的内容 - 即终端(或者在您的情况下,是 cron 的输入)。

  1. /home/john/import.sh 2> >(tee ... ) 1>&3 3>&-

该脚本会将其 stdout 定向到 fd 3 所保存的内容(即指向cat. 的管道),并将 stderr 定向到 tee。

它将关闭 fd 3,因为不再需要它 - 脚本的标准输出已经转到 3 所持有的内容。

  1. tee /proc/self/fd/3

tee 将把两者写入 fd 3 所保存的内容(同样,管道到cat到终端(或者在你的情况下,作为 cron 的输入)。

  1. (...) |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:~$ 

相关内容