Exécuter un travail cron manuellement et immédiatement

108

(J'ai déjà lu Comment puis-je tester un nouveau script cron?. )

J'ai un problème spécifique (le travail cron ne semble pas s'exécuter correctement), mais le problème est général: j'aimerais déboguer les scripts cronés. Je suis conscient que je peux configurer une ligne crontab * * * * * *, mais ce n'est pas une solution pleinement satisfaisante. J'aimerais pouvoir exécuter un travail cron à partir de la ligne de commande comme s'il était exécuté par cron (même utilisateur, mêmes variables d'environnement, etc.). Y a-t-il un moyen de faire cela? Il n'est pas pratique de devoir attendre 60 secondes pour tester les modifications de script.

Pistos
la source
(désolé, impossible d'ajouter un commentaire) 0 30 16 20 *? * Même si vous exécutez le travail comme ça, l'idée est de fournir une sortie de script pour voir ce qui ne va pas, à moins que le travail ne soit écrit dans un journal, ceci est pratiquement inutile

Réponses:

80

Voici ce que j'ai fait, et cela semble fonctionner dans cette situation. Au moins, cela me montre une erreur, alors que courir depuis la ligne de commande car l’utilisateur n’affiche pas l’erreur.


Étape 1 : Je mets cette ligne temporairement dans la crontab de l'utilisateur:

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

puis l'a sorti une fois que le fichier a été écrit.

Étape 2 : Je me suis fait un petit script bash en run-as-cron contenant:

#!/bin/bash
/usr/bin/env -i $(cat /home/username/tmp/cron-env) "[email protected]"

Alors, en tant qu'utilisateur en question, j'ai pu

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

Cette solution pourrait évidemment être étendue pour utiliser sudo ou autre pour plus de flexibilité.

J'espère que cela aide les autres.

Pistos
la source
8
Cela ne fonctionne pas pour moi et je me demande si cela convient à quelqu'un qui a voté. 1) Pourquoi utilisez-vous bash? Ce n'est pas nécessaire ici et il se peut qu'il ne soit pas situé dans /usr/bin. 2) Les cat …/cron-envsorties multi-lignes, ce qui ne fonctionne pas. Essayez simplement d’exécuter /usr/bin/env -i $(cat cron-env) echo $PATHdans le terminal, il sort l’environnement littéralement au lieu de l’utiliser. 3) L’environnement actuel se déverse dans l’environnement cron émulé. Essayez: export foo=leaked; run-as-cron echo $foo.
Marco
@Marco Fonctionne dans bash, c'est ce que j'utilise car c'est un environnement mieux défini que sh. J'utilise tout de pdksh, ksh (plusieurs versions), bash et dash, je suis donc très conscient des différences entre les implémentations de "pure" of sh, même si je reste très strictement dans le sous-ensemble commun des langues. :-)
Max Murphy
7
@Marco 2. génère catplusieurs lignes, ce qui fonctionne, car la substitution de shell les réduit en une seule ligne, sur laquelle vous pouvez vérifier echo $(cat cron-env ) | wc; votre exemple de commande, /usr/bin/env -i $(cat cron-env) echo $PATHsubstitue $PATHdu shell appelant; au lieu de cela, il devrait invoquer un sous-shell à substituer dans le sous-environnement, par exemple /usr/bin/env -i $(cat cron-env) /bin/sh -c 'echo $PATH'. 3. Vous avez fait la même erreur, en substituant à nouveau dans la coquille d'appel au lieu de dans le sous-environnement
John Freeman
41

Je présente une solution basée sur la réponse de Pistos, mais sans les défauts.

  • Ajoutez la ligne suivante à la crontab, par exemple en utilisant crontab -e

    * * * * *  /usr/bin/env > /home/username/cron-env
    
  • Créez un script shell qui exécute une commande dans le même environnement que les tâches cron exécutées:

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

Utilisation:

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

par exemple

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

Notez que le second argument doit être cité s'il nécessite un argument. La première ligne du script charge un shell POSIX en tant qu'interpréteur. La deuxième ligne source le fichier d’environnement cron. Cela est nécessaire pour charger le shell correct, qui est stocké dans la variable d'environnement SHELL. Ensuite, il charge un environnement vide (pour éviter toute fuite de variables d’environnement dans le nouveau shell), lance le même shell qui est utilisé pour les tâches cron et charge les variables d’environnement cron. Enfin, la commande est exécutée.

Marco
la source
cela m'a aidé à reproduire mon erreur de chargement du sphinx liée au rubis.
Cweiske
1
J'ai utilisé l'option @reboot cron pour écrire le fichier cron-env. Vous pouvez ensuite le laisser dans la crontab et il ne sera réécrit qu'au démarrage du système. Cela simplifie un peu la tâche car vous n'avez pas à ajouter / supprimer de lignes.
Michael Barton
Oui, la solution Pistos n'a pas fonctionné pour moi, mais cela a fonctionné
Stack
19

Comme crontab ne fait pas le travail, vous manipulerez son contenu:

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

Ce qu'il fait :

  • liste les travaux crontab
  • supprimer les lignes de commentaire
  • supprimer la configuration de la crontab
  • puis lancez-les un à un
Django Janny
la source
5
Cela ne se fait pas nécessairement dans le même environnement que cron, cependant, et je pensais qu'il ne voulait tester qu'un seul d'entre eux.
Falcon Momot
2
correct, je me suis trompé ... Il ne fait que lancer les travaux mais pas comme cron le ferait!
Django Janny
5
toujours une solution géniale +1
Eric Uldall
1
Vous pouvez juste sudo -H -u otheruser bash -c 'crontab..." lancer la crontab d’un autre utilisateur
Freedo
5

Par défaut avec la plupart des démons cron par défaut que j'ai vus, il n'y a tout simplement aucun moyen de dire à cron de fonctionner ici et maintenant. Si vous utilisez anacron, il est peut-être possible d’exécuter une instance distincte au premier plan.

Si vos scripts ne fonctionnent pas correctement, vous ne prenez pas en compte le fait que

  • le script est exécuté en tant qu'utilisateur particulier
  • cron a un environnement restreint (la manifestation la plus évidente est un chemin différent).

De crontab (5):

Plusieurs variables d'environnement sont configurées automatiquement par le démon cron (8). SHELL est défini sur / bin / sh, et LOGNAME et HOME sont définis à partir de la ligne / etc / passwd du propriétaire de la crontab. PATH est défini sur "/ usr / bin: / bin". HOME, SHELL et PATH peuvent être remplacés par les paramètres de la crontab; LOGNAME est l'utilisateur à partir duquel le travail est en cours d'exécution et ne peut pas être modifié.

En général, PATH est le plus gros problème. Vous devez donc:

  • Définissez explicitement le PATH dans le script, lors du test, sur / usr / bin: / bin. Vous pouvez le faire en bash avec export PATH = "/ usr / bin: / bin"
  • Définissez explicitement le bon PATH que vous voulez en haut de la crontab. Par exemple, PATH = "/ usr / bin: / bin: / usr / local / bin: / usr / sbin: / sbin"

Si vous devez exécuter le script en tant qu'utilisateur sans shell (par exemple, www-data), utilisez sudo:

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

La première chose à tester avant tout cela, bien sûr, est que votre script fait ce qu'il est censé faire depuis la ligne de commande. Si vous ne pouvez pas l'exécuter depuis la ligne de commande, cela ne fonctionnera évidemment pas avec cron.

Philip Reynolds
la source
Merci pour la réponse approfondie. Je suis conscient des deux problèmes liés à l'exécution en tant qu'utilisateur particulier et dans un environnement particulier. En tant que tel, j'ai formulé ma propre réponse, que je vais maintenant publier ...
Pistos
Les caractères d'échappement sont des raisons valables pour que le travail ne soit pas exécuté
Joe Phillips
2

Le script de Marco n'a pas fonctionné pour moi pour une raison quelconque. Je n'avais pas le temps de déboguer, alors j'ai écrit un script Python qui fait la même chose. C'est plus long, mais: premièrement, cela fonctionne pour moi et deuxièmement, je le trouve plus facile à comprendre. Remplacez "/ tmp / cron-env" par l'emplacement où vous avez enregistré votre environnement. C'est ici:

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

Eh bien, l'utilisateur est le même que celui que vous avez mis dans l'entrée de la crontab (ou la crontab dans laquelle vous l'avez insérée, alternativement), c'est donc une évidence. crontab(5) devrait vous donner la liste des variables d'environnement définies, il n'y en a que quelques-unes.

femme
la source
En d'autres termes, vous dites qu'il n'y a aucun moyen de le faire? Seulement des solutions de contournement "assez proches"?
Pistos
Non, je dis que vous pouvez le faire en utilisant les informations que j'ai fournies dans ma réponse.
womble
1

Dans la plupart des crontabs, comme par exemple vixie-cron, vous pouvez placer des variables dans la crontab elle-même, puis utiliser / usr / bin / env pour vérifier si cela fonctionne. De cette façon, vous pouvez faire fonctionner votre script dans crontab une fois que vous avez découvert ce qui ne va pas avec le script run-as-cron.

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

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

La solution de Marco n'a pas fonctionné pour moi, mais le script python de Noam a fonctionné. Voici une légère modification du script de Marco qui l'a fait fonctionner pour moi:

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

Les set -avariables d’exportation ajoutées définies dans le script $ 1 et mises à la disposition de la commande $ 2

Le psy de python de Noam a fonctionné car il "exportait" l'environnement vers le processus enfant.

Facture
la source
1

Si c'est un script shell, cela devrait vous rendre la majeure partie du chemin:

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>

Cela soulignera certainement certains problèmes, sinon tout.

Matthew Wilcoxson
la source
0

Je n'ai jamais trouvé le moyen d'exécuter manuellement des tâches cron, mais cette rédaction suggère de définir le même environnement que celui que cronjob aurait et d'exécuter le script manuellement.

oneodd1
la source
N’est-ce pas ce que vous suggérez de faire ce que le PO veut savoir faire?
womble
C'est pourquoi j'ai inclus le lien vers la description qui décrit comment le faire. Je n'ai pas jugé nécessaire de tout copier-coller ici.
oneodd1
0

vous pouvez programmer le travail pour commencer la minute suivante :)

AdrP
la source
7
59 secondes, c'est beaucoup de temps.
Stéphane Bruckert
Le PO a mentionné cette possibilité dans la question: "Y a-t-il un moyen de faire cela? Il n'est pas pratique d'attendre 60 secondes pour tester les modifications du script."
Andrew Grimm
59 secondes représentent probablement moins de temps qu'il n'en faudrait pour sélectionner et mettre en œuvre l'une des solutions proposées (sans garantie de fonctionnement). Quand je vois de telles lacunes, je me demande comment Linux est devenu un système d'exploitation serveur standard de facto. Un administrateur système sérieux ne voudrait-il pas tester son travail?
Rolf
0

Je nouais sur la réponse de Marco. Le code est indiqué ci-dessous, mais je maintiendrai ce script ici .

Étant donné cette 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"

Exemple de session d'utilisation:

$ 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

C'est cronTest2ce qui doit être appelé correctement pour configurer les variables d'environnement de la même manière que le fait 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" 

cronTests'exécute cronTest2avec le jeu de variables d'environnement approprié:

#!/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
la source