Uruchamianie zadania cron ręcznie i natychmiast

108

(Przeczytałem już Jak mogę przetestować nowy skrypt cron? )

Mam konkretny problem (zadanie CRON nie wydaje się działać lub działa poprawnie), ale problem jest ogólny: chciałbym debugować skrypty, które są kontrolowane. Wiem, że mogę skonfigurować linię crontab * * * * *, ale nie jest to w pełni zadowalające rozwiązanie. Chciałbym móc uruchomić zadanie cron z wiersza poleceń, tak jakby uruchomiło je cron (ten sam użytkownik, te same zmienne środowiskowe itp.). Czy jest na to sposób? Nie trzeba czekać 60 sekund na przetestowanie zmian skryptu.

Pistacje
źródło
(przepraszam, nie mogę dodać komentarza) 0 30 16 20 *? * nawet jeśli wykonasz takie zadanie, cały pomysł polega na dostarczeniu skryptu wyjściowego, aby zobaczyć, co się dzieje, chyba że zadanie zapisze się w dzienniku, jest to dość bezużyteczne

Odpowiedzi:

80

Oto co zrobiłem i wydaje się, że działa w tej sytuacji. Przynajmniej pokazuje mi błąd, podczas gdy uruchamiany z wiersza poleceń, ponieważ użytkownik nie pokazuje błędu.


Krok 1 : Tymczasowo umieszczam ten wiersz w crontab użytkownika:

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

następnie wyjął go po zapisaniu pliku.

Krok 2 : Zrobiłem sobie mały skrypt bash typu run-as-cron zawierający:

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

Tak więc, jako dany użytkownik, byłem w stanie

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

To rozwiązanie można oczywiście rozszerzyć, aby korzystać z sudo lub podobnego rozwiązania dla większej elastyczności.

Mam nadzieję, że to pomaga innym.

Pistacje
źródło
8
To nie działa dla mnie i zastanawiam się, czy to działa dla każdego, kto głosował. 1) Dlaczego używasz bash? Nie jest to wymagane i może nie być zlokalizowane /usr/bin. 2) cat …/cron-envWyprowadza wiele linii, co nie działa. Po prostu spróbuj uruchomić /usr/bin/env -i $(cat cron-env) echo $PATHw terminalu, środowisko wyjściowe dosłownie zamiast go używać. 3) Obecne środowisko wycieka do emulowanego środowiska cron. Spróbuj: export foo=leaked; run-as-cron echo $foo.
Marco
@Marco Działa w bash, którego używam, ponieważ jest to lepiej zdefiniowane środowisko niż sh. Używam wszystkiego od pdksh, ksh (wiele wersji), bash i dash, więc jestem bardzo świadomy różnic między implementacjami „czystego” sh, nawet gdy pozostaję bardzo ściśle we wspólnym podzbiorze języków. :-)
Max Murphy
7
@Marco 2. catwyprowadza wiele wierszy, które działają, ponieważ podstawienie powłoki zrzuca je do jednej linii, którą można sprawdzić echo $(cat cron-env ) | wc; twoje przykładowe polecenie, /usr/bin/env -i $(cat cron-env) echo $PATHzastępuje $PATHpowłokę wywołującą; zamiast tego powinien wywoływać podpowłokę w celu podstawienia w podśrodowisku, np /usr/bin/env -i $(cat cron-env) /bin/sh -c 'echo $PATH'. 3. Popełniłeś ten sam błąd, ponownie zastępując powłokę wywołującą zamiast podśrodowiska
John Freeman
41

Przedstawiam rozwiązanie oparte na odpowiedzi Pistos, ale bez wad.

  • Dodaj następujący wiersz do crontab, np. Używając crontab -e

    * * * * *  /usr/bin/env > /home/username/cron-env
    
  • Utwórz skrypt powłoki, który wykonuje polecenie w tym samym środowisku, w którym uruchamiane są zadania cron:

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

Posługiwać się:

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

na przykład

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

Zauważ, że drugi argument musi zostać zacytowany, jeśli wymaga argumentu. Pierwszy wiersz skryptu ładuje powłokę POSIX jako interpreter. Drugi wiersz pobiera plik środowiska cron. Jest to wymagane do załadowania poprawnej powłoki, która jest przechowywana w zmiennej środowiskowej SHELL. Następnie ładuje puste środowisko (aby zapobiec wyciekaniu zmiennych środowiskowych do nowej powłoki), uruchamia tę samą powłokę, która jest używana dla cronjobs i ładuje zmienne środowiskowe cron. Na koniec polecenie jest wykonywane.

Marco
źródło
pomogło mi to odtworzyć mój błąd ładowania sfinksa związany z rubinem.
cweiske
1
Użyłem opcji @reboot cron do napisania pliku cron-env. Następnie możesz zostawić go w pliku crontab, a zostanie on przepisany dopiero po uruchomieniu systemu. To sprawia, że ​​jest to trochę prostsze, ponieważ nie musisz dodawać / usuwać linii.
Michael Barton
Tak, rozwiązanie Pistos nie działało dla mnie, ale tak się stało
Stack Underflow
19

Ponieważ crontab nie wykonuje zadania, będziesz manipulować jego zawartością:

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

Co to robi :

  • wyświetla listę zadań crontab
  • usuń wiersze komentarza
  • usuń konfigurację crontab
  • następnie uruchom je jeden po drugim
Django Janny
źródło
5
Niekoniecznie robi to jednak w tym samym środowisku, co zrobiłby cron, i pomyślałem, że chciał przetestować tylko jeden z nich.
Falcon Momot,
2
Zgadzam się, pomyliłem się ... Uruchamia tylko zadania, ale nie tak, jak zrobiłby to cron!
Django Janny,
5
wciąż niesamowite rozwiązanie +1
Eric Uldall
1
Możesz po prostu sudo -H -u otheruser bash -c 'crontab..." uruchomić crontab innego użytkownika btw
Freedo
5

Domyślnie w przypadku większości domyślnych demonów cron, które widziałem, po prostu nie ma sposobu, aby powiedzieć cronowi, aby teraz działał tutaj. Jeśli używasz anakronu, być może możliwe jest uruchomienie osobnej instancji na pierwszym planie.

Jeśli twoje skrypty nie działają poprawnie, nie bierzesz tego pod uwagę

  • skrypt działa jako określony użytkownik
  • cron ma ograniczone środowisko (najbardziej oczywistym tego przejawem jest inna ścieżka).

Z crontab (5):

Kilka zmiennych środowiskowych jest konfigurowanych automatycznie przez demona cron (8). SHELL jest ustawiony na / bin / sh, a LOGNAME i HOME są ustawiane z wiersza / etc / passwd właściciela crontab. PATH jest ustawione na „/ usr / bin: / bin”. HOME, SHELL i PATH mogą zostać zastąpione przez ustawienia w crontab; LOGNAME to użytkownik, od którego uruchomione jest zadanie, i nie można go zmienić.

Ogólnie ŚCIEŻKA jest największym problemem, dlatego musisz:

  • Jawnie ustaw PATH w skrypcie podczas testowania na / usr / bin: / bin. Możesz to zrobić w bashie za pomocą PATH eksportu = "/ usr / bin: / bin"
  • Wyraźnie ustaw odpowiednią ŚCIEŻKĘ na górze tabeli crontab. np. PATH = "/ usr / bin: / bin: / usr / local / bin: / usr / sbin: / sbin"

Jeśli chcesz uruchomić skrypt jako inny użytkownik bez powłoki (np. Www-data), użyj sudo:

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

Pierwszą rzeczą do przetestowania przed tym wszystkim jest oczywiście to, że skrypt faktycznie robi to, co powinien, z wiersza poleceń. Jeśli nie możesz uruchomić go z wiersza poleceń, to oczywiście nie będzie działał z cronem.

Philip Reynolds
źródło
Dziękuję za dokładną odpowiedź. Mam świadomość dwóch problemów związanych z działaniem jako konkretny użytkownik i w określonym środowisku. Jako taki sformułowałem własną odpowiedź, którą teraz opublikuję ...
Pistos,
Znaki specjalne to ważne powody, dla których zadanie nie działa
Joe Phillips
2

Z jakiegoś powodu skrypt Marco nie działał dla mnie. Nie miałem czasu na debugowanie, więc napisałem skrypt w języku Python, który robi to samo. Jest dłuższy, ale: po pierwsze, działa dla mnie, a po drugie, łatwiej mi zrozumieć. Zmień „/ tmp / cron-env” na miejsce, w którym zapisałeś swoje środowisko. Oto on:

#!/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()
Noam
źródło
1

Cóż, użytkownik jest taki sam jak ten, który umieściłeś we wpisie crontab (lub na którego crontab wkładasz go naprzemiennie), więc nie ma wątpliwości. crontab(5) powinien dać ci listę ustawionych zmiennych środowiskowych, jest tylko kilka.

womble
źródło
Innymi słowy, mówisz, że nie ma sposobu, aby to zrobić? Tylko obejścia „wystarczająco blisko”?
Pistos,
Nie, mówię, że możesz to zrobić, korzystając z informacji podanych w mojej odpowiedzi.
womble
1

W większości crontabów, takich jak np. Vixie-cron, możesz umieścić zmienne w samym crontabie w ten sposób, a następnie użyć / usr / bin / env, aby sprawdzić, czy zadziałało. W ten sposób możesz sprawić, by skrypt działał w crontab, gdy dowiesz się, co jest nie tak ze skryptem run-as-cron.

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

* * * * *   /usr/bin/env > /home/username/cron-env
Marc Elser
źródło
1

Rozwiązanie Marco nie działało dla mnie, ale skrypt Pythona Noama działał. Oto niewielka modyfikacja skryptu Marco, która sprawiła, że ​​zadziałał dla mnie:

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

Dodano set -azmienne eksportu zdefiniowane w skrypcie $ 1 i udostępniono je do polecenia $ 2

ps Pyton Noama działał, ponieważ „eksportował” środowisko do procesu potomnego.

Rachunek
źródło
1

Jeśli jest to skrypt powłoki, powinno to zapewnić ci większość możliwości:

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>

Na pewno uwypukli niektóre problemy, jeśli nie wszystko.

Matthew Wilcoxson
źródło
0

Nigdy nie znalazłem sposobu na ręczne uruchamianie zadań crona, ale ten zapis sugeruje ustawienie tego samego środowiska, które miałoby cronjob i ręczne uruchomienie skryptu.

oneodd1
źródło
Czy nie sugerujesz, aby robić to, co PO chce wiedzieć?
womble
Właśnie dlatego dołączyłem link do zapisu, który opisuje, jak to zrobić. Nie uważałem za konieczne kopiowania i wklejania wszystkiego tutaj.
oneodd1,
0

możesz zaprogramować pracę, aby rozpocząć następną minutę :)

AdrP
źródło
7
59 sekund to dużo czasu.
Stéphane Bruckert
OP wspomniał o tej możliwości w pytaniu: „Czy istnieje sposób, aby to zrobić? Nie trzeba czekać 60 sekund na przetestowanie zmian skryptu”.
Andrew Grimm,
59 sekund to prawdopodobnie mniej niż zajęłoby wybranie i wdrożenie dowolnego z innych proponowanych (i nie gwarantowanych) rozwiązań. Kiedy widzę takie niedociągnięcia, zastanawiam się, jak Linux stał się tak de facto standardowym systemem operacyjnym serwera. Czy poważny sysadmin nie chciałby przetestować swojej pracy?
Rolf
0

Zastanawiałem się nad odpowiedzią Marco. Kod pokazano poniżej, ale utrzymam ten skrypt tutaj .

Biorąc pod uwagę ten 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"

Przykładowa sesja użytkowania:

$ 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

To cronTest2, co musi być prawidłowo wywołany skonfigurować zmienne środowiskowe w taki sam sposób, jak robi 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" 

cronTestdziała cronTest2z ustawionymi odpowiednimi zmiennymi środowiskowymi:

#!/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"
Mike Slinn
źródło