(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 fonctionner ou ne fonctionne pas correctement), mais le problème est général: je voudrais déboguer des scripts qui sont cronned. Je suis conscient que je peux configurer une ligne de crontab * * * * *, mais ce n'est pas une solution entièrement satisfaisante. Je voudrais pouvoir exécuter un travail cron à partir de la ligne de commande comme si cron l'exécutait (même utilisateur, mêmes variables d'environnement, etc.). Y a-t-il un moyen de faire cela? Avoir à attendre 60 secondes pour tester les changements de script n'est pas pratique.
Voici ce que j'ai fait, et cela semble fonctionner dans cette situation. Au moins, cela me montre une erreur, tandis que l'exécution à partir de la ligne de commande car l'utilisateur n'affiche pas l'erreur.
Étape 1: Je mets temporairement cette ligne dans la crontab de l'utilisateur:
* * * * * /usr/bin/env > /home/username/tmp/cron-env
puis je l'ai retiré une fois le fichier écrit.
Étape 2: Je me suis fait un petit script bash run-as-cron contenant:
#!/bin/bash
/usr/bin/env -i $(cat /home/username/tmp/cron-env) "$@"
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 similaire pour plus de flexibilité.
J'espère que cela aide les autres.
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 ex. 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 deuxième argument doit être cité s'il nécessite un argument. La première ligne du script charge un shell POSIX comme 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 la fuite de variables d'environnement dans le nouveau Shell), lance le même Shell qui est utilisé pour les cronjobs et charge les variables d'environnement cron. Enfin, la commande est exécutée.
Comme crontab ne fait pas le travail, vous devrez manipuler son contenu:
crontab -l | grep -v '^#' | cut -f 6- -d ' ' | while read CMD; do eval $CMD; done
Ce qu'il fait :
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 s'exécuter ici même maintenant. Si vous utilisez anacron, il peut être possible, je pense, d'exécuter une instance distincte au premier plan.
Si vos scripts ne fonctionnent pas correctement, vous ne tenez pas compte du fait que
Depuis 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 s'exécute et ne peut pas être modifié.
En général, PATH est le plus gros problème, vous devez donc:
Si vous devez exécuter le script en tant qu'un autre 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 à partir de la ligne de commande. Si vous ne pouvez pas l'exécuter à partir de la ligne de commande, cela ne fonctionnera évidemment pas avec cron.
Le script de Marco n'a pas fonctionné pour moi pour une raison quelconque. Je n'ai pas eu le temps de déboguer, j'ai donc écrit un script Python qui fait la même chose. C'est plus long, mais: premièrement, ça marche pour moi, et deuxièmement, je trouve plus facile de Modifiez "/ tmp/cron-env" à l'endroit où vous avez enregistré votre environnement. Le voici:
#!/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()
S'il s'agit d'un script Shell, cela devrait vous aider la plupart du temps:
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 mettra certainement en évidence certains problèmes, sinon tout.
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 variables d'exportation set -a
Ajoutées définies dans le script $ 1 et rendues disponibles pour commander $ 2
p.s. python de Noam a fonctionné car il a "exporté" l'environnement vers le processus enfant.
Eh bien, l'utilisateur est le même que celui que vous avez mis dans l'entrée crontab (ou dont vous avez mis la crontab, alternativement), donc c'est une évidence. crontab
(5) devrait vous donner la liste des variables d'environnement définies, il n'y en a que quelques-unes.
Dans la plupart des crontabs comme par ex. vixie-cron vous pouvez placer des variables dans le crontab lui-même comme ceci et ensuite utiliser/usr/bin/env pour vérifier si cela a fonctionné. 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
vous pouvez programmer le travail pour démarrer la minute suivante :)
J'ai nouillé sur la réponse de Marco. Le code est montré ci-dessous, mais je maintiendrai ce script ici .
Compte tenu de ce 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 cronTest2
, qui doit être correctement invoqué pour configurer les variables d'environnement de la même manière que 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
exécute cronTest2
avec les variables d'environnement appropriées définies:
#!/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"
Inspiré par la réponse de @ DjangoJanny, voici ce que j'utilise pour lancer le travail à la 5ème ligne de mon crontab
:
eval "$(crontab -l | sed -n '5p' | tr -s ' ' | cut -d' ' -f 6-)"
Explication:
Je n'ai jamais trouvé de moyen d'exécuter les tâches cron manuellement mais this l'écriture suggère de définir le même environnement que le cronjob aurait et d'exécuter le script manuellement.