(我已经阅读了如何测试新的cron脚本?。)

我有一个特定的问题(cron作业似乎无法运行或运行正常),但该问题很普遍:我想调试破旧的脚本。我知道我可以设置* * * * * crontab行,但这并不是一个完全令人满意的解决方案。我希望能够从命令行运行cron作业,就像cron在运行它一样(相同的用户,相同的环境变量等)。有没有办法做到这一点?必须等待60秒才能测试脚本更改。

评论

(抱歉,无法添加评论)0 30 16 20 *? *即使您像这样运行作业,整个想法是提供脚本输出以查看出了什么问题,除非作业将其写入日志,否则这是无用的

#1 楼

这是我所做的,并且在这种情况下似乎可行。至少,它向我显示了一个错误,而由于用户没有显示该错误而从命令行运行。

* * * * *   /usr/bin/env > /home/username/tmp/cron-env


文件写入后就取出来了。 :

#!/bin/bash
/usr/bin/env -i $(cat /home/username/tmp/cron-env) "$@"



然后,作为有问题的用户,我能够

run-as-cron /the/problematic/script --with arguments --and parameters


显然,可以扩展此解决方案以使用sudo等,以提高灵活性。

希望对其他人有所帮助。

评论


这对我不起作用,我想知道是否对任何支持的人都有效。 1)为什么要使用bash?这里不是必需的,它可能不在/ usr / bin中。 2)cat…/ cron-env输出多行,这是行不通的。只需尝试在终端中执行/ usr / bin / env -i $(cat cron-env)echo $ PATH,它将按字面输出环境,而不使用它。 3)当前环境泄漏到模拟cron环境中。试试:export foo = leaked; cron run echo $ foo。

–马可
2013年9月24日18:33



@Marco在bash中工作,这是我使用的,因为它是比sh更好的定义环境。我使用了从pdksh,ksh(多个版本),bash和破折号开始的所有内容,因此我非常清楚sh的“ pure”实现之间的差异,即使非常严格地使用通用语言子集也是如此。 :-)

–马克斯·墨菲(Max Murphy)
16-3-21在16:53

@Marco 2. cat输出有效的多行,因为shell替换将它们折叠成一行,您可以使用echo $(cat cron-env)检查。厕所;您的示例命令/ usr / bin / env -i $ {cat cron-env)echo $ PATH,从调用shell替换$ PATH;相反,它应该调用一个子外壳来替换子环境,例如/ usr / bin / env -i $(cat cron-env)/ bin / sh -c'echo $ PATH'。 3.您犯了同样的错误,再次替换为调用shell,而不是替换为子环境。

–约翰·弗里曼(John Freeman)
16年6月21日在17:17



#2 楼

我提出了一个基于Pistos答案的解决方案,但没有缺陷。



在crontab中添加以下行,例如使用crontab -e

* * * * *  /usr/bin/env > /home/username/cron-env



创建一个shell脚本,该脚本在与cron作业运行相同的环境中执行命令:

#!/bin/sh

. ""
exec /usr/bin/env -i "$SHELL" -c ". ; "



使用:

run-as-cron <cron-environment> <command>


例如

run-as-cron /home/username/cron-env 'echo $PATH'


请注意如果需要第二个参数,则需要用引号引起来。
脚本的第一行将POSIX shell用作解释器。第二行提供cron环境文件。加载正确的外壳程序是必需的,该外壳程序存储在环境变量SHELL中。然后,它加载一个空环境(以防止环境变量泄漏到新的shell中),启动用于cronjobs的同一shell并加载cron环境变量。最后执行命令。

评论


这有助于我重现与红宝石有关的狮身人面像加载错误。

– cweiske
2013年12月9日7:52

我使用了@reboot cron选项来编写cron-env文件。然后,您可以将其保留在crontab中,并且仅在系统启动时才将其重写。由于您不必添加/删除行,因此它变得更简单。

–迈克尔·巴顿(Michael Barton)
15年7月1日在12:22

是的,Pistos解决方案对我不起作用,但是确实可以

–堆栈下溢
19年2月21日在23:27

#3 楼

由于crontab不起作用,您将操纵它的内容:

crontab -l | grep -v '^#' | cut -f 6- -d ' ' | while read CMD; do eval $CMD; done


它的作用列出crontab作业
删除注释行
删除crontab配置
然后逐个启动它们


评论


不过,这并不一定要在cron所处的环境中做到这一点,而且我认为他只想测试其中的一个。

–猎鹰Momot
13年8月20日在4:39

没错,我误会了……它只运行作业,但不像cron那样!

– Django Janny
2013年9月2日19:53在

还是很棒的解决方案+1

–埃里克·乌德尔(Eric Uldall)
15年6月17日在22:11

您可以只使用sudo -H -u otheruser bash -c'crontab ...”来运行另一个用户的crontab

– Freedo
19-09-14在21:46

#4 楼

默认情况下,对于我所见过的大多数默认cron守护程序,都没有办法告诉cron立即在此处运行。如果您使用的是anacron,则我可能有可能在前台运行一个单独的实例。

如果您的脚本运行不正常,那么您就没有考虑到


脚本以特定用户身份运行
cron受限制的环境(最明显的体现是一条不同的路径)。

从crontab(5):


设置了几个环境变量
由cron(8)
守护程序自动启动。 SHELL设置为/ bin / sh,而
LOGNAME和HOME从crontab所有者的
/ etc / passwd行设置。路径设置为“ / usr / bin:/ bin”。

crontab中的设置可能会覆盖
HOME,SHELL和PATH; LOGNAME是运行
的用户,并且不能更改。



通常,PATH是最大的问题,因此您需要:


在测试时,将脚本中的PATH明确设置为/ usr / bin:/ bin。您可以在bash中使用export PATH =“ / usr / bin:/ bin”

在crontab的顶部显式设置所需的正确PATH。例如PATH =“ / usr / bin:/ bin:/ usr / local / bin:/ usr / sbin:/ sbin”

如果需要以没有外壳程序的其他用户身份运行脚本(例如www -data),请使用sudo:

sudo -u www-data /path/to/crontab-script.sh


当然,首先要测试的第一件事是您的脚本实际上完成了应该从中执行的操作命令行。如果您无法从命令行运行它,则显然无法在cron中运行。

评论


感谢您的全面答复。我知道以特定用户身份和特定环境运行的两个问题。因此,我制定了自己的答案,我现在将其发布...

–手枪
09年11月18日在14:33

转义字符是作业未运行的有效原因

–乔·菲利普斯(Joe Phillips)
2015年6月1日17:54

#5 楼

由于某种原因,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()


#6 楼

Marco的解决方案对我不起作用,但是Noam的python脚本起作用。这是对Marco脚本的稍作修改,使其对我有用: >
ps Noam的python之所以起作用,是因为它将环境“导出”到了子进程。

评论


您可以通过运行./run-as-cron path / to / cron-env'env'来检查是否需要此功能。如果显示的内容与path / to / cron-env所包含的内容不同,则该命令(在这种情况下:env本身实际上并未在与env相同的环境中运行。

–困惑
20/12/29在17:15



#7 楼

我以马可的答案为准。代码如下所示,但我将在此处维护此脚本。

给出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 "\n"; fi
}

function isValidLineNumber {
  #  - number of lines
  #  - requested line number
  if [[ -n "${2//[0-9]+/}" ]] && ((  <=  )); then echo true; else echo false; fi
}

function isVariableAssignment {
  [[ "$( echo "" | 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
  #
  #  - line number to run

  readarray CONTENTS < "$TEMP_FILE"
  for (( i=0; i<=; i++ )); do
    # >&2 echo "$i=$i, $1=, isVariableAssignment: $( isVariableAssignment $CONTENTS[$i] ), CONTENTS[$i]=${CONTENTS[$i]}"
    if isVariableAssignment ${CONTENTS[$i]} || (( $i ==  )); 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=
  local -r TEMP_FILE="$( mktemp crontabTest.XXX )"
  trap deleteTempFile EXIT

  makeTempCrontab
  local -r LINE_NUMBER="$( selectLine )"
  runUpToLine $LINE_NUMBER
}

doIt "" 


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"


#8 楼

好吧,该用户与您在crontab条目中输入的用户相同(或者您在crontab条目中交替输入的用户),所以这很容易。 crontab(5)应该给您设置环境变量的列表,只有少数几个。

评论


换句话说,您是说没有办法吗?只有“足够接近”的解决方法?

–手枪
09年11月18日在14:13

不,我是说您可以使用我在回答中提供的信息来做到这一点。

–womble♦
09年11月18日在14:28

#9 楼

在大多数crontab中,例如您可以像这样在viron-cron中将变量放置在crontab中,然后使用/ usr / bin / env来检查它是否起作用。这样,一旦发现run-as-cron脚本出了什么问题,就可以使脚本在crontab中工作。

SHELL=/bin/bash
LANG=en
FASEL=BLA

* * * * *   /usr/bin/env > /home/username/cron-env


#10 楼

如果它是一个shell脚本,那么应该会带给您大部分的信息:

#11 楼

我还没有找到手动运行cron作业的方法,但是本文建议设置与cronjob相同的环境并手动运行脚本。

评论


您不是建议OP执行OP想知道的操作吗?

–womble♦
09年11月18日在14:11

这就是为什么我包括描述如何执行操作的文章链接。我认为没有必要在此处复制粘贴所有内容。

–oneodd1
09年11月18日在14:40

#12 楼

您可以对作业进行编程以在下一分钟开始:)

评论


59秒是很多时间。

–StéphaneBruckert
15年11月4日在16:47

OP在问题中提到了这种可能性:“有没有办法做到这一点?必须等待60秒才能测试脚本更改,这是不现实的。”

–安德鲁·格林(Andrew Grimm)
16-4-7的1:45

59秒可能比选择和实施任何其他建议的(但不能保证工作)的解决方案要少。当我看到这样的缺点时,我想知道Linux如何成为事实上的标准服务器OS。任何认真的系统管理员都不想测试他们的工作吗?

–罗夫
18年1月15日在16:08



您错过了重点。许多认真的系统管理员不得不调试载体中的数千个cron任务。他们通常还需要多次运行作业,然后才能发现错误。现在算一下。顺便说一句...许多认真的系统管理员宁愿花时间解决有趣的问题,然后等待cron任务开始。像回复您的评论一样,而不是解决该cron工作...:D

– Kepi
20 Mar 2 '20 at 9:54

#13 楼

受@DjangoJanny的答案启发,这是我用来在我的crontab的第5行启动工作的方法:

 eval "$(crontab -l | sed -n '5p' | tr -s ' ' | cut -d' ' -f 6-)"


说明:
启动以下命令: />

#14 楼

以cron的身份运行任务非常棘手。
它需要一个经过修改的环境,一个非交互式外壳,没有附加的输入端子以及可能还需要一个特定的外壳(例如bin/sh代替/bin/bash)。 />我制作了一个脚本来处理所有这些问题。使用命令/脚本运行它以作为第一个参数运行,您很高兴。它还是托管的(可能还会在Github中进行更新)。

#!/bin/bash
# Run as if it was called from cron, that is to say:
#  * with a modified environment
#  * with a specific shell, which may or may not be bash
#  * without an attached input terminal
#  * in a non-interactive shell

function usage(){
    echo "q4312078q - Run a script or a command as it would be in a cron job, then display its output"
    echo "Usage:"
    echo "   q4312078q [command | script]"
}

if [ "" == "-h" -o "" == "--help" ]; then
    usage
    exit 0
fi

if [ $(whoami) != "root" ]; then
    echo "Only root is supported at the moment"
    exit 1
fi

# This file should contain the cron environment.
cron_env="/root/cron-env"
if [ ! -f "$cron_env" ]; then
    echo "Unable to find $cron_env"
    echo "To generate it, run \"/usr/bin/env > /root/cron-env\" as a cron job"
    exit 0
fi

# It will be a nightmare to expand "$@" inside a shell -c argument.
# Let's rather generate a string where we manually expand-and-quote the arguments
env_string="/usr/bin/env -i "
for envi in $(cat "$cron_env"); do
   env_string="${env_string} $envi "
done

cmd_string=""
for arg in "$@"; do
    cmd_string="${cmd_string} \"${arg}\" "
done

# Which shell should we use?
the_shell=$(grep -E "^SHELL=" /root/cron-env | sed 's/SHELL=//')
echo "Running with $the_shell the following command: $cmd_string"


# Let's route the output in a file
# and do not provide any input (so that the command is executed without an attached terminal)
so=$(mktemp "/tmp/fakecron.out.XXXX")
se=$(mktemp "/tmp/fakecron.err.XXXX")
"$the_shell" -c "$env_string $cmd_string" >"$so" 2>"$se" < /dev/null

echo -e "Done. Here is 3[1mstdout3[0m:"
cat "$so"
echo -e "Done. Here is 3[1mstderr3[0m:"
cat "$se"
rm "$so" "$se"