(我已经阅读了如何测试新的cron脚本?。)
我有一个特定的问题(cron作业似乎无法运行,或无法正常运行),但问题很普遍:我想调试已损坏的脚本。我知道我可以设置* * * * * crontab行,但这并不是一个完全令人满意的解决方案。我希望能够从命令行运行cron作业,就像cron在运行它一样(相同的用户,相同的环境变量等)。有没有办法做到这一点?必须等待60秒才能测试脚本更改是不切实际的。
(我已经阅读了如何测试新的cron脚本?。)
我有一个特定的问题(cron作业似乎无法运行,或无法正常运行),但问题很普遍:我想调试已损坏的脚本。我知道我可以设置* * * * * crontab行,但这并不是一个完全令人满意的解决方案。我希望能够从命令行运行cron作业,就像cron在运行它一样(相同的用户,相同的环境变量等)。有没有办法做到这一点?必须等待60秒才能测试脚本更改是不切实际的。
Answers:
这是我所做的,并且在这种情况下似乎可行。至少,它向我显示了一个错误,而由于用户未显示该错误而从命令行运行。
步骤1:我将此行临时放在用户的crontab中:
* * * * * /usr/bin/env > /home/username/tmp/cron-env
然后在文件写入后将其取出。
第2步:为自己制作了一个小型的cash运行bash脚本,其中包含:
#!/bin/bash
/usr/bin/env -i $(cat /home/username/tmp/cron-env) "$@"
因此,作为有问题的用户,我能够
run-as-cron /the/problematic/script --with arguments --and parameters
显然,可以扩展此解决方案以使用sudo或此类以提高灵活性。
希望这对其他人有帮助。
/usr/bin
。2)cat …/cron-env
输出多行,这是行不通的。只需尝试/usr/bin/env -i $(cat cron-env) echo $PATH
在终端中执行,它便会直接输出环境而不是使用环境。3)当前环境泄漏到模拟cron环境中。试试:export foo=leaked; run-as-cron echo $foo
。
cat
输出有效的多行,因为shell替换将它们折叠成一行,您可以使用echo $(cat cron-env ) | wc
; 您的示例命令,从调用shell /usr/bin/env -i $(cat cron-env) echo $PATH
替代$PATH
;相反,它应该调用一个子外壳来替换子环境,例如/usr/bin/env -i $(cat cron-env) /bin/sh -c 'echo $PATH'
。3.您犯了同样的错误,再次替换为调用外壳而不是子环境
我提出了一个基于Pistos答案的解决方案,但没有缺陷。
将以下行添加到crontab中,例如使用 crontab -e
* * * * * /usr/bin/env > /home/username/cron-env
创建一个shell脚本,该脚本在与cron作业运行相同的环境中执行命令:
#!/bin/sh
. "$1"
exec /usr/bin/env -i "$SHELL" -c ". $1; $2"
采用:
run-as-cron <cron-environment> <command>
例如
run-as-cron /home/username/cron-env 'echo $PATH'
请注意,如果需要第二个参数,则需要用引号引起来。脚本的第一行加载POSIX Shell作为解释器。第二行提供cron环境文件。加载正确的外壳程序是必需的,该外壳程序存储在环境变量中SHELL
。然后,它加载一个空环境(以防止环境变量泄漏到新的shell中),启动用于cronjobs的同一shell并加载cron环境变量。最后执行命令。
由于crontab不能完成这项工作,因此您需要操纵它的内容:
crontab -l | grep -v '^#' | cut -f 6- -d ' ' | while read CMD; do eval $CMD; done
它能做什么 :
sudo -H -u otheruser bash -c 'crontab..."
运行另一个用户的crontab btw
默认情况下,对于我所见过的大多数默认cron守护程序,都没有办法告诉cron立即在此处运行。如果您使用的是anacron,则我认为有可能在前台运行一个单独的实例。
如果您的脚本运行不正常,那么您就没有考虑到
从crontab(5):
cron(8)守护程序会自动设置几个环境变量。SHELL设置为/ bin / sh,LOGNAME和HOME从crontab所有者的/ etc / passwd行设置。PATH设置为“ / usr / bin:/ bin”。HOME,SHELL和PATH可能会被crontab中的设置覆盖;LOGNAME是作业从中运行的用户,并且不能更改。
通常,PATH是最大的问题,因此您需要:
如果需要以没有外壳程序的其他用户身份运行脚本(例如www-data),请使用sudo:
sudo -u www-data /path/to/crontab-script.sh
当然,首先要测试的第一件事是您的脚本实际上完成了应该从命令行执行的操作。如果您无法从命令行运行它,则显然无法在cron中运行。
由于某种原因,Marco的脚本对我不起作用。我没有时间进行调试,所以我写了一个Python脚本来做同样的事情。它更长,但是:首先,它对我有用,其次,我发现它更容易理解。将“ / tmp / cron-env”更改为保存环境的位置。这里是:
#!/usr/bin/env python
from __future__ import division, print_function
import sys
import os
def main():
if len(sys.argv) != 2 or sys.argv[1] in ('-h', '--help'):
print("Usage: {} CMD\n"
"Run a command as cron would. Note that CMD must be quoted to be only one argument."
.format(sys.argv[0]))
sys.exit(1)
_me, cmd = sys.argv
env = dict(line.strip().split('=', 1) for line in open('/tmp/cron-env'))
sh = env['SHELL']
os.execvpe(sh, [sh, '-c', cmd], env)
if __name__ == '__main__':
main()
在大多数诸如vixie-cron之类的crontab中,您可以像这样将变量放置在crontab中,然后使用/ usr / bin / env来检查它是否起作用。这样,一旦发现run-as-cron脚本出了什么问题,就可以使脚本在crontab中工作。
SHELL=/bin/bash
LANG=en
FASEL=BLA
* * * * * /usr/bin/env > /home/username/cron-env
您可以将作业编程为在下一分钟开始:)
我以马可的答案为准。代码如下所示,但是我将在此处维护此脚本。
鉴于此crontab:
# m h dom mon dow command
X=Y
1 2 3 4 5 6 echo "Hello, world"
1 2 3 4 5 6 echo "Goodby, cruel world"
1 2 3 4 5 6 echo "Please spare me the drama"
用法示例会话:
$ cronTest
This is the crontab for without comment lines or blank lines:
1 X=Y
2 echo "Hello, world"
3 echo "Goodby, cruel world"
4 echo "Please spare me the drama"
Which line would you like to run as now?
55
55 is not valid, please enter an integer from 1 to 4
2
Evaluating 1: X=Y
Evaluating 2: echo "Hello, world"
Hello, world
这是cronTest2
,需要像cron一样正确地调用它来设置环境变量:
#!/bin/bash
# Prompt user for a user crontab entry to execute
function deleteTempFile {
rm -f $TEMP_FILE
}
function debug {
if [ "$DEBUG" ]; then >&2 printf "$1\n"; fi
}
function isValidLineNumber {
# $1 - number of lines
# $2 - requested line number
if [[ -n "${2//[0-9]+/}" ]] && (( $2 <= $1 )); then echo true; else echo false; fi
}
function isVariableAssignment {
[[ "$( echo "$1" | grep "=" )" ]]
}
function makeTempCrontab {
local -r ASTERISK=\\*
local -r NUMBER='[[:digit:]]{1,2}'
local -r NUMBERS="$NUMBER(,$NUMBER)+"
local -r CRON="^(($ASTERISK|$NUMBER|$NUMBERS)[[:space:]]+)"
local -r CRON5_REGEX="$CRON{5}"
local -r CRON6_REGEX="$CRON{6}"
rm -f "$TEMP_FILE"
local -r ALL_LINES="$( crontab -l )"
# Ignore empty lines and lines starting with # (comment lines)
local -r LINES="$(
echo "$ALL_LINES" | \
grep -v '^[[:space:]]*#' | \
grep -v '^[[:space:]]*$'
)"
if [[ -z "$LINES" ]]; then
echo "Your crontab is empty, nothing to do"
exit 1
fi
IFS=$'\n'
for LINE in $LINES; do
LINE="$( echo "$LINE" | sed 's/\s\+$//e' )" # remove trailing space
if [ "$( echo "$LINE" | grep "^$" )" ]; then
debug "" # ignore empty line
elif [ "$( echo "$LINE" | egrep "$CRON6_REGEX" )" ]; then
debug "6 field date/time specifier: $LINE"
# strip out when to run debug, leaving just the command to execute
echo "$LINE" | cut -f 7- -d ' ' >> "$TEMP_FILE"
elif [ "$( echo "$LINE" | egrep "$CRON5_REGEX" )" ]; then
debug "5 field date/time specifier: $LINE"
# strip out when to run debug, leaving just the command to execute
echo "$LINE" | cut -f 6- -d ' ' >> "$TEMP_FILE"
elif [ "$( echo "$LINE" | grep '^@' )" ]; then
debug "@declaration: $LINE"
# strip out @declaration, leaving just the command to execute
echo "$LINE" | cut -f 2- -d ' ' >> "$TEMP_FILE"
elif [ "$( echo "$LINE" | grep '=' )" ]; then
debug "Variable assignment: $LINE"
echo "$LINE" >> "$TEMP_FILE"
else
debug "Ignored: $LINE"
fi
done
unset IFS
}
function runUpToLine {
# Scans up to given line number in $TEMP_FILE
# Evaluates variable assignment
# Executes specified line
# Ignores remainder of file
# Function definitions are not supported
#
# $1 - line number to run
readarray CONTENTS < "$TEMP_FILE"
for (( i=0; i<=$1; i++ )); do
# >&2 echo "\$i=$i, \$1=$1, isVariableAssignment: $( isVariableAssignment $CONTENTS[$i] ), CONTENTS[$i]=${CONTENTS[$i]}"
if isVariableAssignment ${CONTENTS[$i]} || (( $i == $1 )); then
printf "\nEvaluating $(( i+1 )): ${CONTENTS[$i]}"
eval "${CONTENTS[$i]}"
fi
done
}
function selectLine {
>&2 echo "This is the crontab for $USER without comment lines or blank lines:"
cat -n "$TEMP_FILE" >&2
>&2 echo "Which line would you like to run as $USER now?"
local -r NUM_LINES=$( cat "$TEMP_FILE" | wc -l )
read LINE_NUMBER
# >&2 echo "NUM_LINES=$NUM_LINES, LINE_NUMBER=$LINE_NUMBER; valid: $( isValidLineNumber $NUM_LINES $LINE_NUMBER )"
while [[ $( isValidLineNumber $NUM_LINES $LINE_NUMBER ) == false ]]; do
>&2 echo "$LINE_NUMBER is not valid, please enter an integer from 1 to $NUM_LINES"
read LINE_NUMBER
# >&2 echo "NUM_LINES=$NUM_LINES, LINE_NUMBER=$LINE_NUMBER; valid: $( isValidLineNumber $NUM_LINES $LINE_NUMBER )"
done
(( LINE_NUMBER-- ))
echo ${LINE_NUMBER}
}
function doIt {
export USER=$1
local -r TEMP_FILE="$( mktemp crontabTest.XXX )"
trap deleteTempFile EXIT
makeTempCrontab
local -r LINE_NUMBER="$( selectLine )"
runUpToLine $LINE_NUMBER
}
doIt "$1"
cronTest
运行cronTest2
设置适当的环境变量:
#!/bin/bash
# Execute a user crontab entry with the proper environment
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
env -i bash --noprofile --norc -c "$DIR/cronTest2 $USER"