Einen Cron-Job manuell und sofort ausführen

108

(Ich habe bereits gelesen, wie ich ein neues Cron-Skript testen kann .)

Ich habe ein bestimmtes Problem (Cron-Job scheint nicht oder nicht ordnungsgemäß zu laufen), aber das Problem ist allgemein: Ich möchte Scripts debuggen, die cronned sind. Mir ist bewusst, dass ich eine * * * * * crontab-Leitung einrichten kann, aber das ist keine völlig zufriedenstellende Lösung. Ich möchte in der Lage sein, einen Cron-Job über die Befehlszeile auszuführen, als würde Cron ihn ausführen (derselbe Benutzer, dieselben Umgebungsvariablen usw.). Gibt es eine Möglichkeit, dies zu tun? Es ist nicht praktisch, 60 Sekunden warten zu müssen, um Skriptänderungen zu testen.

Pistos
quelle
(Kann leider keinen Kommentar hinzufügen) 0 30 16 20 *? * Selbst wenn Sie den Job wie das laufen, die ganze Idee Skript - Ausgabe , um zu sehen , was falsch läuft , wenn der Job schreibt in ein Protokoll, das ist quuiet nutzlos zu schaffen ist

Antworten:

80

Hier ist, was ich getan habe, und es scheint in dieser Situation zu funktionieren. Zumindest wird mir ein Fehler angezeigt, wohingegen das Ausführen von der Befehlszeile aus, da der Benutzer den Fehler nicht anzeigt.


Schritt 1 : Ich habe diese Zeile vorübergehend in die crontab des Benutzers eingefügt:

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

dann nahm es heraus, sobald die Datei geschrieben wurde.

Schritt 2 : Ich habe ein kleines Run-as-Cron-Bash-Skript erstellt, das Folgendes enthält:

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

Also war ich als fraglicher Benutzer in der Lage

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

Diese Lösung könnte offensichtlich erweitert werden, um sudo oder dergleichen für mehr Flexibilität zu verwenden.

Hoffe das hilft anderen.

Pistos
quelle
8
Das funktioniert bei mir nicht und ich frage mich, ob es bei jemandem funktioniert, der gestimmt hat. 1) Warum benutzt du Bash? Es ist hier nicht erforderlich und befindet sich möglicherweise nicht in /usr/bin. 2) Die cat …/cron-envAusgabe mehrerer Zeilen funktioniert nicht. Versuchen Sie einfach, das Programm /usr/bin/env -i $(cat cron-env) echo $PATHim Terminal auszuführen . Es gibt die Umgebung buchstäblich aus, anstatt sie zu verwenden. 3) Die aktuelle Umgebung gelangt in die emulierte Cron-Umgebung. Versuchen: export foo=leaked; run-as-cron echo $foo.
Marco
@Marco Funktioniert in bash, was ich benutze, da es eine besser definierte Umgebung als sh ist. Ich benutze alles von pdksh, ksh (mehrere Versionen), bash und dash, daher bin ich mir der Unterschiede zwischen den Implementierungen von "pure" von sh sehr bewusst, auch wenn ich mich streng an die gemeinsame Teilmenge der Sprachen halte. :-)
Max Murphy
7
@Marco 2. catgibt mehrere Zeilen aus, die funktionieren, da die Shell-Ersetzung sie zu einer einzigen Zeile zusammenfasst, die Sie überprüfen können echo $(cat cron-env ) | wc. Ihr Beispielbefehl,, /usr/bin/env -i $(cat cron-env) echo $PATHersetzt $PATHdie aufrufende Shell. Stattdessen sollte eine Unterschale aufgerufen werden, die in der Unterumgebung ersetzt werden soll, z /usr/bin/env -i $(cat cron-env) /bin/sh -c 'echo $PATH'. 3. Sie haben den gleichen Fehler begangen, indem Sie erneut in der aufrufenden Shell und nicht in der Unterumgebung eingesetzt haben
John Freeman,
41

Ich präsentiere eine Lösung basierend auf Pistos Antwort, aber ohne die Mängel.

  • Fügen Sie der Crontab die folgende Zeile hinzu, z. B. mit crontab -e

    * * * * *  /usr/bin/env > /home/username/cron-env
    
  • Erstellen Sie ein Shell-Skript, das einen Befehl in derselben Umgebung ausführt, in der Cron-Jobs ausgeführt werden:

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

Verwenden:

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

z.B

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

Beachten Sie, dass das zweite Argument in Anführungszeichen gesetzt werden muss, wenn ein Argument erforderlich ist. Die erste Zeile des Skripts lädt eine POSIX-Shell als Interpreter. Die zweite Zeile enthält die Cron-Umgebungsdatei. Dies ist erforderlich, um die richtige Shell zu laden, die in der Umgebungsvariablen gespeichert ist SHELL. Anschließend wird eine leere Umgebung geladen (um zu verhindern, dass Umgebungsvariablen in die neue Shell gelangen), dieselbe Shell gestartet, die für Cronjobs verwendet wird, und die Cron-Umgebungsvariablen geladen. Schließlich wird der Befehl ausgeführt.

Marco
quelle
Dies half mir, meinen rubinbedingten Sphinx-Ladefehler zu reproduzieren.
Cweiske
1
Ich habe die Option @reboot cron verwendet, um die cron-env-Datei zu schreiben. Sie können es dann in der crontab belassen und es wird erst beim Systemstart neu geschrieben. Dies macht es ein wenig einfacher, da Sie keine Zeilen hinzufügen / entfernen müssen.
Michael Barton
Ja, die Pistos-Lösung hat bei mir nicht funktioniert, aber das hat funktioniert
Stack Underflow
19

Da Crontab den Job nicht erledigt, müssen Sie den Inhalt manipulieren:

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

Was es macht :

  • listet crontab jobs auf
  • Kommentarzeilen entfernen
  • Entfernen Sie die Crontab-Konfiguration
  • Starten Sie sie dann nacheinander
Django Janny
quelle
5
Dies muss jedoch nicht unbedingt in der gleichen Umgebung geschehen, in der cron es tun würde, und ich dachte, er wollte nur einen von ihnen testen.
Falcon Momot
2
richtig, ich habe mich geirrt ... es laufen nur die jobs aber nicht so wie es cron machen würde!
Django Janny
5
Immer noch eine großartige Lösung +1
Eric Uldall
1
Sie können nur sudo -H -u otheruser bash -c 'crontab..." die Crontab eines anderen Benutzers BTW
Freedo
5

Standardmäßig gibt es bei den meisten Standard-Cron-Daemons, die ich gesehen habe, einfach keine Möglichkeit, Cron anzuweisen, sofort hier zu laufen. Wenn Sie anacron verwenden, kann es sein, dass ich denke, dass im Vordergrund eine separate Instanz ausgeführt wird.

Wenn Ihre Skripte nicht ordnungsgemäß ausgeführt werden, berücksichtigen Sie dies nicht

  • Das Skript wird als bestimmter Benutzer ausgeführt
  • cron hat eine eingeschränkte Umgebung (die offensichtlichste Manifestation davon ist ein anderer Weg).

Aus crontab (5):

Mehrere Umgebungsvariablen werden vom Daemon cron (8) automatisch eingerichtet. SHELL wird auf / bin / sh gesetzt, und LOGNAME und HOME werden in der Zeile / etc / passwd des Besitzers der crontab festgelegt. PATH ist auf "/ usr / bin: / bin" gesetzt. HOME, SHELL und PATH können durch Einstellungen in der Crontab überschrieben werden. LOGNAME ist der Benutzer, von dem der Job ausgeführt wird und der möglicherweise nicht geändert wird.

Im Allgemeinen ist PFAD das größte Problem, daher müssen Sie Folgendes tun:

  • Setzen Sie den Pfad innerhalb des Skripts beim Testen explizit auf / usr / bin: / bin. Sie können dies in bash mit export PATH = "/ usr / bin: / bin" tun.
  • Stellen Sie den gewünschten Pfad explizit oben auf der Crontab ein. zB PATH = "/ usr / bin: / bin: / usr / local / bin: / usr / sbin: / sbin"

Wenn Sie das Skript als anderer Benutzer ohne Shell ausführen müssen (z. B. www-data), verwenden Sie sudo:

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

Zuallererst sollten Sie natürlich testen, ob Ihr Skript tatsächlich das tut, was es von der Befehlszeile aus tun soll. Wenn Sie es nicht über die Befehlszeile ausführen können, funktioniert es offensichtlich nicht mit cron.

Philip Reynolds
quelle
Vielen Dank für die gründliche Antwort. Ich bin mir der beiden Probleme bewusst, als bestimmter Benutzer und in einer bestimmten Umgebung ausgeführt zu werden. Als solches habe ich meine eigene Antwort formuliert, die ich jetzt posten werde ...
Pistos 18.11.09
Escape-Zeichen sind gültige Gründe dafür, dass der Job nicht ausgeführt wird
Joe Phillips,
2

Marcos Drehbuch funktionierte aus irgendeinem Grund nicht für mich. Ich hatte keine Zeit zum Debuggen, also schrieb ich ein Python-Skript, das dasselbe tut. Es ist länger, aber: Erstens funktioniert es für mich und zweitens finde ich es leichter zu verstehen. Ändern Sie "/ tmp / cron-env" in das Verzeichnis, in dem Sie Ihre Umgebung gespeichert haben. Hier ist es:

#!/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
quelle
1

Nun, der Benutzer ist derselbe wie der, den Sie in den Crontab-Eintrag eingegeben haben (oder in dessen Crontab Sie ihn abwechselnd eingegeben haben). Das ist also ein Kinderspiel. crontab(5) Sollte man die Liste der Umgebungsvariablen einstellen, gibt es nur wenige.

womble
quelle
Mit anderen Worten, Sie sagen, es gibt keine Möglichkeit, dies zu tun? Nur "nahe genug" Workarounds?
Pistos 18.11.09
Nein, ich sage, dass Sie es mit den Informationen tun können, die ich in meiner Antwort angegeben habe.
womble
1

In den meisten Crontabs wie z. B. vixie-cron können Sie Variablen wie folgt in die Crontab selbst einfügen und dann mit / usr / bin / env prüfen, ob sie funktionieren. Auf diese Weise können Sie Ihr Skript in Crontab zum Laufen bringen, sobald Sie herausgefunden haben, was mit dem Run-as-Cron-Skript nicht stimmt.

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

* * * * *   /usr/bin/env > /home/username/cron-env
Marc Elser
quelle
1

Marcos Lösung hat bei mir nicht funktioniert, aber Noams Python-Skript hat funktioniert. Hier ist eine kleine Änderung an Marcos Skript, die es für mich zum Laufen gebracht hat:

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

Die hinzugefügten set -aExportvariablen wurden in Skript $ 1 definiert und für Befehl $ 2 verfügbar gemacht

ps Noams Python hat funktioniert, weil es die Umgebung in den untergeordneten Prozess 'exportiert' hat.

Rechnung
quelle
1

Wenn es sich um ein Shell-Skript handelt, sollten Sie den größten Teil davon erhalten:

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>

Es wird definitiv einige Probleme aufzeigen, wenn nicht sogar alles.

Matthew Wilcoxson
quelle
0

Ich habe noch nie eine Möglichkeit gefunden, Cron-Jobs manuell auszuführen. In diesem Artikel wird jedoch empfohlen, dieselbe Umgebung wie beim Cronjob festzulegen und das Skript manuell auszuführen.

oneodd1
quelle
Schlagen Sie nicht vor, das zu tun, was das OP wissen möchte?
womble
Aus diesem Grund habe ich den Link zu dem Artikel eingefügt, der beschreibt, wie es gemacht wird. Ich hielt es nicht für notwendig, hier alles zu kopieren und einzufügen.
oneodd1
0

Sie können den Job so programmieren, dass er in der nächsten Minute beginnt :)

AdrP
quelle
7
59 Sekunden sind viel Zeit.
Stéphane Bruckert
Das OP erwähnte diese Möglichkeit in der Frage: "Gibt es eine Möglichkeit, dies zu tun? Es ist nicht praktikabel, 60 Sekunden warten zu müssen, um Skriptänderungen zu testen."
Andrew Grimm
59 Sekunden sind wahrscheinlich weniger als nötig, um eine der anderen vorgeschlagenen (und nicht garantiert funktionierenden) Lösungen auszuwählen und zu implementieren. Wenn ich solche Mängel sehe, frage ich mich, wie Linux zu einem solchen De-facto-Standard-Server-Betriebssystem wurde. Würde kein ernsthafter Sysadmin ihre Arbeit testen wollen?
Rolf
0

Ich nudelte an Marcos Antwort. Der Code wird unten gezeigt, aber ich werde dieses Skript hier pflegen .

Angesichts dieser 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"

Beispielgebrauchssitzung:

$ 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

Dies cronTest2muss ordnungsgemäß aufgerufen werden, um die Umgebungsvariablen auf dieselbe Weise wie cron einzurichten:

#!/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" 

cronTestLäuft cronTest2mit den richtigen Umgebungsvariablen:

#!/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
quelle