Использование python3 в скрипте оболочки в crontab

Я пытаюсь запустить скрипт оболочки с помощью crontab, который запускает скрипты python3. 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 выполняется, ничего не происходит, когда я запускаю его вручную, изменения в базе данных происходят, как и ожидалось. Группа имеет те же права, что и я. Файл оболочки может быть выполнен мной, а группа и файлы python не могут быть выполнены мной, поэтому я не знаю, зачем группе это нужно.

PS: я хочу выполнить скрипт python в оболочке, так как у нас есть много скриптов с несколькими аргументами, и, следовательно, crontab станет перенаселенным, и некоторые скрипты должны выполняться в определенном порядке.

РЕДАКТИРОВАТЬ: Добавление exec >> /tmp/output 2>&1 сразу после #! /bin.bash пишет отголоски /tmp/output всякий раз, когда я запускаю его вручную, но не когда я запускаю его в cron, даже не перед запуском какого-либо скрипта на python.

Запуск одного из сценариев Python непосредственно из cron работает, однако даже если я скопирую и вставлю в файл оболочки точно такую ​​же строку, как та, которая работает в cron, ничего не произойдет.

6 ответов

Решение

Есть много компонентов для этой проблемы. Я игнорирую ошибку MTA, потому что это просто уведомление по электронной почте о завершении работы cron. Я также предполагаю, что у вас правильно установлены разрешения и что ваш скрипт работает нормально при запуске вручную в оболочке.

Самая большая проблема заключается в том, что команда CRON отличается от запуска команды из терминальной "оболочки". Вы должны указать, что ваш скрипт запускается с использованием bash. Измените свою работу cron с:

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

чтобы:

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

Этот вопрос имеет больше информации и дополнительных опций для решения проблемы.

Измените эту строку:

*/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.

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,

В настоящее время существует много предположений о проблеме, и это потому, что ваша система не может отправить вам сообщение об ошибке, чтобы точно объяснить, в чем проблема. Некоторое время назад я столкнулся с подобной проблемой, был ошеломлен попыткой настроить реальную почтовую систему, поэтому написал короткую переадресацию почты sendmail: 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 файл).

После этого вы сможете получить фактическую ошибку, и мы можем реально помочь вам

Последняя строка в вашем скрипте bash содержит относительные пути (./) Я считаю, что это проблема

Если вы зарегистрируете вывод скрипта, который вы вызываете, с помощью cron, вы можете легко найти ошибку. Попробуйте что-то вроде этого:

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

Другие вопросы по тегам