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

108

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

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

Pistos
источник
(извините, не могу добавить комментарий) 0 30 16 20 *? * даже если вы выполняете задание таким образом, вся идея состоит в том, чтобы обеспечить вывод сценария, чтобы увидеть, что происходит, если задание не записывается в журнал, это довольно бесполезно

Ответы:

80

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


Шаг 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
источник
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 Работает в bash, и это то, что я использую, так как это более определенная среда, чем sh. Я использую все из pdksh, ksh (несколько версий), bash и dash, так что я хорошо осведомлён о различиях между реализациями «чистого» sh, даже когда остаюсь очень строго в общем подмножестве языков. :-)
Макс Мерфи
7
@Marco 2. catвыводит несколько строк, которые работают, потому что подстановка оболочки сворачивает их в одну строку, которую вы можете проверить echo $(cat cron-env ) | wc; ваш пример команды, /usr/bin/env -i $(cat cron-env) echo $PATHподставляет $PATHиз вызывающей оболочки; вместо этого он должен вызывать подоболочку для подстановки в окружении, например /usr/bin/env -i $(cat cron-env) /bin/sh -c 'echo $PATH'. 3. Вы сделали ту же ошибку, снова заменив ее в вызывающей оболочке, а не в подпространстве
Джон Фриман
41

Я представляю решение, основанное на ответе 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. Затем он загружает пустую среду (чтобы предотвратить утечку переменных среды в новую оболочку), запускает ту же оболочку, которая используется для cronjobs, и загружает переменные среды cron. Наконец команда выполнена.

Marco
источник
это помогло мне воспроизвести мою связанную с рубином ошибку загрузки сфинкса.
cweiske
1
Я использовал опцию @reboot cron для записи файла cron-env. Затем вы можете оставить его в crontab, и он будет перезаписан только при запуске системы. Это немного упрощает, так как вам не нужно добавлять / удалять строки.
Майкл Бартон
Да, решение Pistos не сработало для меня, но это сработало
Stack Underflow
19

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

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

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

  • списки заданий crontab
  • удалить строки комментариев
  • удалить конфигурацию crontab
  • затем запустить их один за другим
Джанго Дженни
источник
5
Это не обязательно делает это в той же среде, что и cron, и я подумал, что он хочет протестировать только один из них.
Сокол Момот
2
правильно, я ошибся ... Он только запускает задания, но не так, как cron!
Джанго Дженни
5
все еще отличное решение +1
Эрик Улдолл
1
Вы можете просто sudo -H -u otheruser bash -c 'crontab..." запустить crontab другого пользователя
Freedo
5

По умолчанию, с большинством демонов 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.

Филип Рейнольдс
источник
Спасибо за подробный ответ. Мне известны две проблемы, связанные с работой как конкретного пользователя, так и с определенной средой. Таким образом, я сформулировал свой собственный ответ, который я сейчас опубликую ...
Pistos
Экранирующие символы являются вескими причинами того, что работа не выполняется
Джо Филлипс
2

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

romble
источник
Другими словами, вы говорите, что нет способа сделать это? Только «достаточно близкие» обходные пути?
Pistos
Нет, я говорю, что вы можете сделать это, используя информацию, которую я предоставил в своем ответе.
womble
1

В большинстве 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
Марк Эльзер
источник
1

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

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

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

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

Билл
источник
1

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

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, и запустить скрипт вручную.

oneodd1
источник
Разве вы не предлагаете делать то, что хочет знать ОП?
womble
Именно поэтому я включил ссылку на статью, которая описывает, как это сделать. Я не считал необходимым копировать и вставлять все здесь.
oneodd1
0

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

AdrP
источник
7
59 секунд это много времени.
Стефан Брукерт
ОП упомянул об этой возможности в вопросе: «Есть ли способ сделать это? Подождать 60 секунд, чтобы проверить изменения сценария, нецелесообразно».
Эндрю Гримм
Вероятно, 59 секунд - это меньше, чем потребовалось бы, чтобы выбрать и реализовать любое из других предложенных (и не гарантированно работающих) решений. Когда я вижу такие недостатки, я удивляюсь, как Linux стала де-факто стандартной серверной ОС. Не хочет ли какой-нибудь серьезный сисадмин проверить свою работу?
Рольф
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"
Майк Слинн
источник