Запуск роботи з крон вручну та негайно


108

(Я вже читав Як я можу протестувати новий сценарій cron? )

У мене є конкретна проблема (завдання Cron не працює або працює належним чином), але проблема є загальною: я хотів би налагоджувати сценарії, які увімкнено. Я усвідомлюю, що можу створити лінію * * * * * crontab, але це не є повністю задовільним рішенням. Я хотів би мати можливість виконувати завдання cron з командного рядка так, як ніби він виконував його (той самий користувач, ті ж змінні середовища тощо). Чи є спосіб це зробити? Доводиться чекати 60 секунд, щоб перевірити зміни сценарію - це не практично.


(вибачте, не можете додати коментар) 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

Це рішення, очевидно, можна було б розширити, щоб використовувати судо або подібні для більшої гнучкості.

Сподіваюся, що це допомагає іншим.


8
Це не працює для мене, і мені цікаво, чи це для когось, хто виступив за це. 1) Для чого ви використовуєте bash? Тут не потрібно, і він може не знаходитися в /usr/bin. 2) cat …/cron-envВиводить кілька рядків, що не працює. Просто спробуйте виконати /usr/bin/env -i $(cat cron-env) echo $PATHв терміналі, він виводить середовище буквально замість цього. 3) Поточне середовище просочується в емульоване середовище крона. Спробуйте: 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

Я представляю рішення, засноване на відповіді Пістоса, але без вад.

  • Додайте наступний рядок до 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. Потім він завантажує порожнє середовище (для запобігання протікання змінних оточуючих середовищ у новій оболонці), запускає ту саму оболонку, яка використовується для cronjob і завантажує змінні середовища cron. Нарешті команда виконується.


це допомогло мені відтворити мою помилку завантаження сфінкса, пов’язану з рубіном.
cweiske

1
Я використовував варіант @reboot cron, щоб написати файл cron-env. Потім ви можете залишити його в crontab, і він буде переписаний лише після запуску системи. Це робить його трохи простішим, оскільки вам не потрібно додавати / видаляти рядки.
Майкл Бартон

Так, рішення Пістоса для мене не спрацювало, але це зробило
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

2
правильно, я помилився ... Він виконує лише завдання, але не так, як це робив би cron!
Джанго Джанні

5
все ще дивовижне рішення +1
Ерік Улдалл

1
Ви можете просто sudo -H -u otheruser bash -c 'crontab..." запустити crontab іншого користувача btw
Freedo

5

За замовчуванням у більшості замовчуваних демонів 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.


Дякую за ретельну відповідь. Мені відомі два питання роботи як певного користувача, так і з певним середовищем. Як такий, я сформулював власну відповідь, яку я зараз опублікую ...
Пістос

Персонажі втечі є поважною причиною того, що робота не працює
Джо Філіпс

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) Якщо вам подано список змінних оточуючих середовищ, їх лише декілька.


Іншими словами, ви говорите, що немає способу це зробити? Лише "досить близькі" обходи?
Пістос

Ні, я кажу, що ви можете це зробити, використовуючи інформацію, яку я надав у своїй відповіді.
живіт

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

Рішення Марко для мене не спрацювало, але сценарій пітона Ноама спрацював. Ось невелика зміна сценарію Марко, яка змусила його працювати для мене:

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

Додані set -aзмінні експорту, визначені в скрипті $ 1, і стали доступними для команди $ 2

ps Ноамовий пітон працював, тому що він 'експортував' середовище для дочірнього процесу.


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

0

ви можете запрограмувати роботу, щоб розпочати наступну хвилину :)


7
59 секунд - це багато часу.
Stéphane Bruckert

ОП згадувала про таку можливість у запитанні: "Чи є спосіб це зробити? Потрібно почекати 60 секунд, щоб перевірити зміни сценарію, це не практично".
Ендрю Грімм

59 секунд, мабуть, менше, ніж потрібно для вибору та впровадження будь-якого з інших запропонованих (і не гарантовано працюючих) рішень. Коли я бачу такі недоліки, мені цікаво, як Linux став такою фактично стандартною ОС сервера. Чи не хотів би жоден серйозний систематик перевірити свою роботу?
Рольф

0

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

Враховуючи цей кронтаб:

# 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"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.