手动并立即运行cron作业


108

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

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


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

Answers:


80

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


步骤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或此类以提高灵活性。

希望这对其他人有帮助。


8
这对我不起作用,我想知道是否对任何支持的人都有效。1)为什么要使用bash?这里不需要它,也可能不在中/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
Marco Marco

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

7
@Marco 2. 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.您犯了同样的错误,再次替换为调用外壳而不是子环境
John Freeman

41

我提出了一个基于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环境变量。最后执行命令。


这帮助我重现了与红宝石有关的狮身人面像加载错误。
cweiske 2013年

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

是的,Pistos解决方案对我而言不起作用,但确实如此
Stack Underflow

19

由于crontab不能完成这项工作,因此您需要操纵它的内容:

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

它能做什么 :

  • 列出crontab作业
  • 删除评论行
  • 删除crontab配置
  • 然后一一发射

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

2
正确,我误会了……它只运行作业,但不像cron那样!
Django Janny

5
仍然是很棒的解决方案+1
Eric Uldall 2015年

1
您只能sudo -H -u otheruser bash -c 'crontab..." 运行另一个用户的crontab btw
Freedo

5

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

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

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

从crontab(5):

cron(8)守护程序会自动设置几个环境变量。SHELL设置为/ bin / sh,LOGNAME和HOME从crontab所有者的/ etc / passwd行设置。PATH设置为“ / usr / bin:/ bin”。HOME,SHELL和PATH可能会被crontab中的设置覆盖;LOGNAME是作业从中运行的用户,并且不能更改。

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

  • 在测试时,将脚本中的PATH明确设置为/ usr / bin:/ bin。您可以使用export PATH =“ / usr / bin:/ bin”在bash中执行此操作
  • 在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中运行。


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

转义字符是作业无法运行的有效原因
Joe Phillips

2

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

1

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


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

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

1

在大多数诸如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

1

Marco的解决方案对我不起作用,但是Noam的python脚本起作用。这是对Marco脚本的略微修改,使其对我有用:

#!/bin/sh
. "$1"
exec /usr/bin/env -i "$SHELL" -c "set -a;. $1; $2"

set -a在脚本$ 1中定义了添加的导出变量,并使其可用于命令$ 2

ps Noam的python之所以起作用,是因为它将环境“导出”到了子进程。


1

如果它是一个shell脚本,这应该可以帮助您:

sudo su  # (assuming it's run as root, if not switch to the user you want it to run as)
cd  # Switch to home folder
sh <full-path/my-shell-script>

如果不是全部,它肯定会突出一些问题。


0

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


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

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

0

您可以将作业编程为在下一分钟开始:)


7
59秒是很多时间。
斯特凡Bruckert

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

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

0

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

鉴于此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"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.