问题 在crontab中的shell脚本中使用python3


我尝试使用运行python3脚本的crontab运行shell脚本。 crontab用于用户组。现在它运行脚本但不运行其中的python3脚本。我尝试调试它,但我无法弄清楚会发生什么。它可能是一个权限问题或路径问题,但我无法弄清楚。 这是行crontab

*/5 * * * * /home/group_name/path/to/script/run.sh

正如我所说的那样,cron工作被执行了,或者至少就是我自从跑步以来的想法 sudo grep CRON /var/log/syslog 我得到像这样的台词

 Feb 16 20:35:01 ip-**-**-*-*** CRON[4947]: (group_name) CMD (/home/group_name/path/to/script/run.sh)

在下面我也得到一条可能与问题有关的线

Feb 16 20:35:01 ip-**-**-*-*** CRON[4946]: (CRON) info (No MTA installed, discarding output)

最后 run.sh 看起来像这样

#!/bin/bash

# get path to script and path to script directory
SCRIPT=$(readlink -f "$0")
SCRIPTPATH=$(dirname "$SCRIPT")

echo "set directory"
cd "$SCRIPTPATH"

echo "run first script"
/usr/bin/python3 ./first_script.py > ./log1.txt

但是当cron作业执行时没有任何反应,当我手动运行它时,数据库的cahnges按预期发生。该集团拥有与我相同的权利。 shell文件可以由我执行,组和python文件不能由我执行,所以我不知道为什么该组需要这个。

PS:我想在shell中执行python脚本,因为我们有很多脚本,有时会有很多参数,因此crontab会变得人满为患,而且某些脚本必须以特定的顺序执行。

编辑: 添加 exec >> /tmp/output 2>&1 之后 #! /bin.bash 写回声 /tmp/output 每当我手动运行它,但不是当我在cron中运行它时,甚至在运行任何python脚本之前都没有。

直接从cron运行其中一个python脚本,但即使我复制粘贴与在cron中工作的行完全相同的行,也没有任何反应。


1987
2018-02-16 20:48


起源

./first_script.py也应该是完全限定的...... - Ivonet
加 exec >> /tmp/output 2>&1 靠近shell脚本的顶部。回来告诉我们内容是什么 /tmp/output 揭示。另外,你从中的内容中学到了什么? /home/group_name/path/to/script/log1.txt ? - Robᵩ
为什么不 /usr/bin/python3 "$SCRIPTPATH/first_script.py" 代替 cd "$SCRIPTPATH" && /usr/bin/python3 ./first_script.py ?无论当前工作目录是什么,模块搜索都将相对于python脚本的路径。 - Paulo Scardine
所以随着 exec 命令我得到了回声 /tmp/output/ 当我手动运行它。我改变了它们,看看下次cron运行时它们是否会更新。我的 log1.txt file包含我的脚本的输出,但只有在我手动运行时才会更新。在一分钟内它会再次运行,但需要一些时间,所以我会稍等一下并给出更新。 - YannickSSE
所以我改变了一些回声 /tmp/output 没有更新eventhoug sudo grep CRON /var/log/syslog 显示cron作业已运行。 - YannickSSE


答案:


这个问题有很多组成部分。我忽略了MTA错误,因为这只是当您的cron作业完成时的电子邮件通知。我还假设您已正确设置权限,并且在shell中手动运行时脚本运行正常。

最大的问题是CRON命令与从终端“shell”运行命令不同。您必须指定使用bash运行脚本。从以下位置更改您的cron作业:

*/5 * * * * /home/group_name/path/to/script/run.sh 

至:

*/5 * * * * bash /home/group_name/path/to/script/run.sh

这个问题 有更多信息和解决问题的其他选项。


3
2018-02-26 16:41



这实际上解决了这个问题,但是感觉就像是一个黑客,我有绝对的路径和一切,但它仍然无法正常工作。现在我拥有像我一样的所有东西并且它有效。 - YannickSSE
我通常用/ bin / bah开始我的会话,但是脚本也没有这样做。所以我真的不明白发生了什么。 - YannickSSE


改变这一行:

*/5 * * * * /home/group_name/path/to/script/run.sh

至:

*/5 * * * * cd /home/group_name/path/to/script && /home/group_name/path/to/script/run.sh

关于/ var / log / syslog,当您查看/ var / log / syslog时,请查看时间戳以确定是否正在运行cron作业。

关于cron作业无法写入log.txt,它可能与权限有关。尝试更改此行:

/usr/bin/python3 ./first_script.py > ./log1.txt

至:

/usr/bin/python3 /full/path/to/first_script.py > /tmp/log1.txt

看看是否有任何区别。 cron应该能够写入/ tmp。


2
2018-02-21 06:21



没有任何内容写入/tmp/log1.txt - YannickSSE
我把上面改为:换了一下。使用first_script.py的完整路径而不是./first_script.py,看看是否有帮助。此外,当您在我的答案顶部建议的cron文件中进行更改时会发生什么 - cd / home / group_name ... && /home/group_name/.../run.sh? - hikerjobs


1)消息“没有安装MTA”并不意味着任何错误,它只表示没有邮件服务器的cron无法报告任何细节。

修改cron作业以记录其输出 进入系统日志

*/5 * * * * /home/group_name/path/to/script/run.sh 2>&1 | /usr/bin/logger -t run.sh

然后通过检查结果 sudo tail -f /var/log/syslog (要么 sudo tail -f /var/log/messages 在RedHat和SuSE上)

或者, 安装Postfix 并将其配置为“仅限本地”交付:

sudo apt-get install postfix

然后检查邮件为 group_name 用户。

2)重定向 > ./log1.txt 在 run.sh 应该在每次执行时覆盖日志文件。如果python脚本失败,则异常 log1.txt将保持截断为零长度。在那种情况下修改最后一行 run.sh

/usr/bin/python3 ./first_script.py 2>&1 > ./log1.txt

并检查结果。

如果 log1.txt 既没有被截断也没有包含新的输出,那么根本没有启动python脚本。参考步骤1)调试 run.sh


2
2018-02-20 23:05



我做到了它仍然无法正常工作。 - YannickSSE
关于我的建议 readlink 和 dirname 路径被证明是错误的。我编写了答案来描述可能的调试方法。 - void


bash脚本中的最后一行包含相对路径(./) 我相信这是问题所在


1
2018-02-16 20:56



对我而言,相对路径似乎是有目的的。这是询问道路的重点 $0 和 cd到那个地方。但你可能是对的。 - Robᵩ
我从不同的位置运行shell文件,它始终有效。所以这应该不是问题。这是一个极小的工作示例,我之所以如此 cd - 到那个位置是我运行几个脚本,我希望shell脚本尽可能可读。 - YannickSSE
我仍然尝试绝对路径并得到同样的问题。 - YannickSSE


目前有很多关于这个问题的猜测,那是因为您的系统无法向您发送故障电子邮件以准确解释问题所在。一段时间我遇到了类似的问题,试图建立一个实际的邮件系统不知所措,所以写了一个短邮件转发 发送邮件 站在: pygeon_mail

#!/usr/bin/python
from __future__ import with_statement
from email.mime.text import MIMEText
import email
import os
import pwd
import smtplib
import stat
import sys
import syslog
import traceback


CONFIG = '/etc/pygeon_mail.rc'
# example config file
#
# server=mail.example.com
# port=25
# domain=example.com
# host=this_pc_host_name
# root=me@example.com,you@example.com
# ethan=me@example.com
# debug=debug@example.com


def check_dangerously_writable(filename):
    "return the bits of group/other that are writable"
    mode = stat.S_IMODE(os.stat(filename)[0])       # get the mode bits
    if mode & (stat.S_IWGRP | stat.S_IWOTH):        # zero means not set
    syslog.syslog("%s must only be writable by root, aborting" % (filename, ))
    sys.exit(1)

def get_config(filename, config=None):
    "return settings from config file"
    check_dangerously_writable(filename)
    if config is None:
    config = {}
    with open(filename) as settings:
    for line in settings:
        line = line.strip()
        if line and line[:1] != '#':
        key, value = line.split('=')
        key, value = key.strip(), value.strip()
        config[key] = value
    return config

def mail(server, port, sender, receiver, subject, message):
    """sends email.message to server:port

    receiver is a list of addresses
    """
    msg = MIMEText(message.get_payload())
    for address in receiver:
    msg['To'] = address
    msg['From'] = sender
    msg['Subject'] = subject
    for header, value in message.items():
    if header in ('To','From', 'Subject'):
        continue
    msg[header] = value
    smtp = smtplib.SMTP(server, port)
    try:
    send_errs = smtp.sendmail(msg['From'], receiver, msg.as_string())
    except smtplib.SMTPRecipientsRefused as exc:
    send_errs = exc.recipients
    smtp.quit()
    if send_errs:
    errs = {}
    for user in send_errs:
        if '@' not in user:
        errs[user] = [send_errs[user]]
        continue
        server = 'mail.' + user.split('@')[1]
        smtp = smtplib.SMTP(server, 25)
        try:
        smtp.sendmail(msg['From'], [user], msg.as_string())
        except smtplib.SMTPRecipientsRefused as exc:
        if send_errs[user] != exc.recipients[user]:
            errs[user] = [send_errs[user], exc.recipients[user]]
        else:
            errs[user] = [send_errs[user]]
        smtp.quit()
    for user, errors in errs.items():
        for code, response in errors:
        syslog.syslog('%s --> %s: %s' % (user, code, response))
    return errs


if __name__ == '__main__':
    syslog.openlog('pygeon', syslog.LOG_PID)
    try:
    config = get_config(CONFIG)
    root = config.get('root')
    domain = config.get('domain', '')
    if domain:
        domain = '@' + domain
    sender = None
    receiver = []
    dot_equals_blank = False
    ignore_rest = False
    next_arg_is_subject = False
    redirect = False
    subject = ''
    for i, arg in enumerate(sys.argv[1:]):
        if next_arg_is_subject:
        subject = arg
        next_arg_is_subject = False
        sys.argv[i] = '"%s"' % (arg, )
        elif arg == '-s':
        next_arg_is_subject = True
        elif arg == '-i':
        dot_equals_blank = True
        elif arg[:2] == '-F':
        sender = arg[2:]
        elif arg[0] != '-':
        receiver.append(arg)
        else:
        pass
    command_line = ' '.join(sys.argv)
    if sender is None:
        sender = pwd.getpwuid(os.getuid()).pw_name
    sender = '%s@%s' % (sender, config['host'])
    if not receiver:
        receiver.append(pwd.getpwuid(os.getuid()).pw_name)
    limit = len(receiver)
    for i, target in enumerate(receiver):
        if i == limit:
        break
        if '@' not in target:
        receiver[i] = ''
        receiver.extend(config.get(target, target+domain).split(','))
    receiver = [r for r in receiver if r]
    server = config['server']
    port = int(config['port'])
    all_data = []
    text = []
    while True:
        data = sys.stdin.read()
        if not data:
        break
        all_data.append(data)
        if ignore_rest:
        continue
        for line in data.split('\n'):
        if line == '.':
            if dot_equals_blank:
            line = ''
            else:
            ignore_rest = True
            break
        text.append(line)
    text = '\n'.join(text)
    message = email.message_from_string(text)
    errs = mail(server, port, sender, receiver, subject, message)
    except Exception:
    exc, err, tb = sys.exc_info()
    lines = traceback.format_list(traceback.extract_tb(tb))
    syslog.syslog('Traceback (most recent call last):')
    for line in lines:
        for ln in line.rstrip().split('\n'):
        syslog.syslog(ln)
    syslog.syslog('%s: %s' % (exc.__name__, err))
    sys.exit(1)
    else:
    receiver = []
    debug_email = config.get('debug', None)
    if debug_email:
        receiver.append(debug_email)
    if errs and root not in receiver:
        receiver.append(root)
    if receiver:
        debug = [
            'command line:',
            '-------------',
            repr(command_line),
            '-' * 79,
            '',
            'sent email:',
            '-----------',
            text,
            '-' * 79,
            '',
            'raw data:',
            '---------',
            ''
            ]
        all_data = ''.join(all_data)
        while all_data:
        debug_text, all_data = repr(all_data[:79]), all_data[79:]
        debug.append(debug_text)
        debug.append('-' * 79)
        if errs:
        debug.extend([
            '',
            'errors:',
            '-------',
            ])
        for address, error in sorted(errs.items()):
            debug.append('%r: %r' % (address, error))
        debug.append('-' * 79)
        text = '\n'.join(debug)
        message = email.message_from_string(text)
        mail(server, port, 'debug@%s' % config['host'], receiver, subject+'  [pygeon_mail debugging info]', message)
    if errs:
        sys.exit(1)

它是为Python 2.5编写的,应该适用于2.6和2.7。

它需要被复制到 /usr/sbin/sendmail 权限为0755并由root拥有:

sudo cp pygeon_mail / usr / sbin / sendmail

sudo chown root:root / usr / sbin / sendmail

sudo chmod 0755 / usr / sbin / sendmail

你需要创建一个 /etc/pygeon_mail.rc 配置文件(参见代码示例)。

然后,您可以使用以下内容进行测试:

$ echo一些有用的信息| sendmail我自己 - “一些重要的主题”

并且您希望在您的普通电子邮件帐户(您在中设置的电子邮件帐户)中看到该电子邮件 /etc/pygeon_mail.rc  文件)。

在那之后,你应该能够得到实际的错误,我们实际上可以帮助你


1
2018-02-26 20:21



错误的问题? - AK47
不。获得实际错误是第一步。 - Ethan Furman
我需要安装一个venv然后尝试一下但是我不是那么容易做到这一点,一旦我们设置了测试环境,我可能会尝试这个。 - YannickSSE


如果您记录使用cron调用的脚本的输出,则可以非常轻松地找到错误。尝试这样的事情:

*/10 * * * * sh /bin/execute/this/script.sh >> /var/log/script_output.log 2>&1


0
2018-02-26 22:15