Запуск задания cron вручную и сразу

(Я уже читал, как я могу протестировать новый скрипт cron?)

У меня есть конкретная проблема (кажется, что задание cron не запускается или работает неправильно), но проблема общая: я хотел бы отладить скрипты cronned. Я знаю, что могу установить * * * * * строку crontab, но это не вполне удовлетворительное решение. Я хотел бы иметь возможность запускать задание cron из командной строки, как если бы оно выполнялось cron (тот же пользователь, те же переменные окружения и т. Д.). Есть ли способ сделать это? Подождать 60 секунд, чтобы проверить изменения скрипта, нецелесообразно.

12 ответов

Решение

Вот что я сделал, и, похоже, работает в этой ситуации. По крайней мере, это показывает мне ошибку, тогда как запуск из командной строки, поскольку пользователь не показывает ошибку.


Шаг 1: Я временно поместил эту строку в crontab пользователя:

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

затем вынул его, как только файл был написан.

Шаг 2: Сделал себе маленький bash-скрипт run-as-cron, содержащий:

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

Итак, как пользователь, я смог

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

Это решение, очевидно, может быть расширено, чтобы использовать sudo или другое для большей гибкости.

Надеюсь, что это помогает другим.

Я представляю решение, основанное на ответе Pistos, но без недостатков.

  • Добавьте следующую строку в crontab, например, используя crontab -e

    * * * * *  /usr/bin/env > /home/username/cron-env
    
  • Создайте сценарий оболочки, который выполняет команду в той же среде, в которой выполняются задания 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 в качестве интерпретатора. Вторая строка содержит файл среды cron. Это необходимо для загрузки правильной оболочки, которая хранится в переменной окружения SHELL, Затем он загружает пустую среду (чтобы предотвратить утечку переменных среды в новую оболочку), запускает ту же оболочку, которая используется для cron jobs, и загружает переменные среды cron. Наконец команда выполнена.

Поскольку crontab не выполняет эту работу, вы будете манипулировать его содержимым:

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

Что оно делает:

  • списки заданий crontab
  • удалить строки комментариев
  • удалить конфигурацию crontab
  • затем запустить их один за другим

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

Если ваши скрипты не работают должным образом, то вы не принимаете во внимание, что

  • скрипт работает от имени конкретного пользователя
  • cron имеет ограниченную среду (наиболее очевидным проявлением этого является другой путь).

Из crontab(5):

Несколько переменных окружения устанавливаются автоматически демоном cron(8). Для SHELL установлено значение / bin / sh, а для LOGNAME и HOME - строка / etc / passwd владельца crontab. PATH установлен в "/usr/bin:/bin". HOME, SHELL и PATH могут быть переопределены настройками в crontab; LOGNAME - это пользователь, от которого выполняется задание, и его нельзя изменить.

В общем, PATH - самая большая проблема, поэтому вам необходимо:

  • Во время тестирования явно укажите PATH в /usr/bin:/bin. Вы можете сделать это в bash с помощью экспорта PATH="/usr/bin:/bin"
  • Явно установите нужный PATH в верхней части crontab. например, PATH="/usr/bin:/bin:/usr/local/bin:/usr/sbin:/sbin"

Если вам нужно запустить скрипт от имени другого пользователя без оболочки (например, www-data), используйте sudo:

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

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

Решение Марко не сработало для меня, но скрипт Python Ноама сработал. Вот небольшая модификация сценария Марко, которая заставила его работать на меня:

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

Добавленный set -a переменные экспорта определены в скрипте $1 и сделаны доступными для команды $2

Питон ps Noam работал, потому что он "экспортировал" среду в дочерний процесс.

Я прижалась к ответу Марко. Код показан ниже, но я буду поддерживать этот скрипт здесь.

Учитывая этот 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"

Сценарий Марко у меня почему-то не сработал. У меня не было времени на отладку, поэтому я написал скрипт на 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()

В большинстве crontabs, таких как, например, vixie-cron, вы можете поместить переменные в сам crontab следующим образом, а затем использовать /usr/bin/env, чтобы проверить, работает ли он. Таким образом, вы можете заставить ваш скрипт работать в crontab, как только вы узнаете, что не так с скриптом run-as-cron.

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

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

Если это сценарий оболочки, он должен пройти большую часть пути:

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>

Это определенно выделит некоторые проблемы, если не все.

Вдохновленный ответом @DjangoJanny, вот что я использую, чтобы запустить работу на 5-й строке моегоcrontab:

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

Объяснение:

  • запускает команду, заданную:
  • отображает crontab
  • получает 5-ю строчку
  • заменяет несколько пробелов одним пробелом
  • берет все с 6-го столбца до конца

Ну, пользователь такой же, как тот, которого вы вводите в запись crontab (или в чей crontab вы добавляете его, поочередно), так что это не составляет труда. crontab(5) должен дать вам список установленных переменных окружения, их всего несколько.

Запускать задачу, как 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 "$0 - Run a script or a command as it would be in a cron job, then display its output"
    echo "Usage:"
    echo "   $0 [command | script]"
}

if [ "$1" == "-h" -o "$1" == "--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 \033[1mstdout\033[0m:"
cat "$so"
echo -e "Done. Here is \033[1mstderr\033[0m:"
cat "$se"
rm "$so" "$se"

Я никогда не находил способ запускать задания cron вручную, но эта статья предлагает установить ту же среду, что и cron job, и запустить скрипт вручную.

Вы можете запрограммировать работу, чтобы начать в следующую минуту:)

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