在 crontab 的 shell 脚本中使用 python3
Using python3 in shell script in crontab
我尝试 运行 一个 shell 脚本与 crontab 哪个 运行s 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 "[=15=]")
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 会变得过多并且有些脚本必须按特定顺序执行。
编辑:
在 #! /bin.bash
之后立即添加 exec >> /tmp/output 2>&1
会在我手动 运行 时将回显写入 /tmp/output
但当我在 cron 中 运行 时则不会,甚至在 /tmp/output
之前 运行宁任意 python 脚本。
运行 直接来自 cron 的 python 脚本之一可以工作,但是即使我将与 cron 中工作的完全相同的行复制粘贴到 shell 文件中, 没有任何反应。
您的 bash 脚本的最后一行包含相对路径 (./)
我相信这是问题所在
1) 消息"No MTA installed" 并不表示任何错误,它仅表示没有邮件服务器cron 无法报告任何详细信息。
修改 cron 作业以记录其输出 into syslog:
*/5 * * * * /home/group_name/path/to/script/run.sh 2>&1 | /usr/bin/logger -t run.sh
然后通过 sudo tail -f /var/log/syslog
(或 RedHat 和 SuSE 上的 sudo tail -f /var/log/messages
)检查结果
或者,install Postfix 并将其配置为 "local only" 交付:
sudo apt-get install postfix
然后以 group_name
用户身份检查邮件。
2) run.sh
中的重定向 > ./log1.txt
应该在每次执行时覆盖日志文件。如果 python 脚本因异常而失败,则 log1.txt
将保持截断为零长度。在这种情况下修改 run.sh
的最后一行:
/usr/bin/python3 ./first_script.py 2>&1 > ./log1.txt
并检查结果。
如果 log1.txt
既没有被截断也没有包含新的输出,那么根本就没有启动 python 脚本。参考步骤1)调试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 作业是否正在 运行。
关于无法写入 log.txt 的 cron 作业,可能与权限有关。尝试更改此行:
/usr/bin/python3 ./first_script.py > ./log1.txt
至:
/usr/bin/python3 /full/path/to/first_script.py > /tmp/log1.txt
看看有没有区别。 cron 应该能够写入 /tmp。
这个问题有很多组成部分。我忽略了 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
This question 有更多信息和解决问题的附加选项。
目前对该问题有很多猜测,那是因为您的系统无法向您发送失败邮件来准确说明问题所在。我 运行 前阵子遇到了类似的问题,试图建立一个实际的邮件系统不知所措,所以写了一个简短的邮件转发 sendmail stand-in: 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 some useful info | sendmail myself -s "some important subject"
您希望在您的普通电子邮件帐户(您在 /etc/pygeon_mail.rc
文件中设置)中看到该电子邮件。
在那之后,你应该可以得到实际的错误,我们可以实际帮助你
如果您记录使用 cron 调用的脚本的输出,您会很容易发现错误。尝试这样的事情:
*/10 * * * * sh /bin/execute/this/script.sh >> /var/log/script_output.log 2>&1
我尝试 运行 一个 shell 脚本与 crontab 哪个 运行s 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 "[=15=]")
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 会变得过多并且有些脚本必须按特定顺序执行。
编辑:
在 #! /bin.bash
之后立即添加 exec >> /tmp/output 2>&1
会在我手动 运行 时将回显写入 /tmp/output
但当我在 cron 中 运行 时则不会,甚至在 /tmp/output
之前 运行宁任意 python 脚本。
运行 直接来自 cron 的 python 脚本之一可以工作,但是即使我将与 cron 中工作的完全相同的行复制粘贴到 shell 文件中, 没有任何反应。
您的 bash 脚本的最后一行包含相对路径 (./) 我相信这是问题所在
1) 消息"No MTA installed" 并不表示任何错误,它仅表示没有邮件服务器cron 无法报告任何详细信息。
修改 cron 作业以记录其输出 into syslog:
*/5 * * * * /home/group_name/path/to/script/run.sh 2>&1 | /usr/bin/logger -t run.sh
然后通过 sudo tail -f /var/log/syslog
(或 RedHat 和 SuSE 上的 sudo tail -f /var/log/messages
)检查结果
或者,install Postfix 并将其配置为 "local only" 交付:
sudo apt-get install postfix
然后以 group_name
用户身份检查邮件。
2) run.sh
中的重定向 > ./log1.txt
应该在每次执行时覆盖日志文件。如果 python 脚本因异常而失败,则 log1.txt
将保持截断为零长度。在这种情况下修改 run.sh
的最后一行:
/usr/bin/python3 ./first_script.py 2>&1 > ./log1.txt
并检查结果。
如果 log1.txt
既没有被截断也没有包含新的输出,那么根本就没有启动 python 脚本。参考步骤1)调试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 作业是否正在 运行。
关于无法写入 log.txt 的 cron 作业,可能与权限有关。尝试更改此行:
/usr/bin/python3 ./first_script.py > ./log1.txt
至:
/usr/bin/python3 /full/path/to/first_script.py > /tmp/log1.txt
看看有没有区别。 cron 应该能够写入 /tmp。
这个问题有很多组成部分。我忽略了 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
This question 有更多信息和解决问题的附加选项。
目前对该问题有很多猜测,那是因为您的系统无法向您发送失败邮件来准确说明问题所在。我 运行 前阵子遇到了类似的问题,试图建立一个实际的邮件系统不知所措,所以写了一个简短的邮件转发 sendmail stand-in: 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 some useful info | sendmail myself -s "some important subject"
您希望在您的普通电子邮件帐户(您在 /etc/pygeon_mail.rc
文件中设置)中看到该电子邮件。
在那之后,你应该可以得到实际的错误,我们可以实际帮助你
如果您记录使用 cron 调用的脚本的输出,您会很容易发现错误。尝试这样的事情:
*/10 * * * * sh /bin/execute/this/script.sh >> /var/log/script_output.log 2>&1