Ejecutar un trabajo cron de forma manual e inmediata

108

(Ya he leído ¿Cómo puedo probar un nuevo script cron? )

Tengo un problema específico (el trabajo cron no parece ejecutarse o no se ejecuta correctamente), pero el problema es general: me gustaría depurar los scripts cronnados. Soy consciente de que puedo configurar una línea * * * * * crontab, pero esa no es una solución totalmente satisfactoria. Me gustaría poder ejecutar un trabajo cron desde la línea de comando como si cron lo estuviera ejecutando (el mismo usuario, las mismas variables de entorno, etc.). ¿Hay alguna forma de hacer esto? Tener que esperar 60 segundos para probar los cambios en el script no es práctico.

Pistos
fuente
(lo siento, no puedo agregar comentarios) 0 30 16 20 *? * incluso si ejecuta el trabajo de esa manera, la idea es proporcionar una salida de script para ver qué está sucediendo a menos que el trabajo escriba en un registro, esto es bastante inútil

Respuestas:

80

Esto es lo que hice, y parece funcionar en esta situación. Al menos, me muestra un error, mientras que se ejecuta desde la línea de comandos ya que el usuario no muestra el error.


Paso 1 : pongo esta línea temporalmente en el crontab del usuario:

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

luego lo sacó una vez que se escribió el archivo.

Paso 2 : me hice un pequeño script bash run-as-cron que contiene:

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

Entonces, como usuario en cuestión, pude

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

Obviamente, esta solución podría ampliarse para hacer uso de sudo o similar para una mayor flexibilidad.

Espero que esto ayude a otros.

Pistos
fuente
8
Esto no funciona para mí y me pregunto si funcionará para alguien que haya votado. 1) ¿Por qué estás usando bash? No se requiere aquí y puede que no se encuentre en /usr/bin. 2) Las cat …/cron-envsalidas de varias líneas, que no funciona. Simplemente intente ejecutar /usr/bin/env -i $(cat cron-env) echo $PATHen el terminal, genera el entorno literalmente en lugar de usarlo. 3) El entorno actual se filtra en el entorno cron emulado. Proveedores: export foo=leaked; run-as-cron echo $foo.
Marco
@Marco funciona en bash, que es lo que uso, ya que es un entorno mejor definido que sh. Utilizo todo, desde pdksh, ksh (varias versiones), bash y dash, así que soy muy consciente de las diferencias entre las implementaciones de "puro" de sh, incluso cuando me mantengo estrictamente en el subconjunto común de los idiomas. :-)
Max Murphy
77
@Marco 2. catgenera varias líneas, que funcionan, porque la sustitución de shell las contrae en una sola línea, con la que puede verificar echo $(cat cron-env ) | wc; su comando de ejemplo /usr/bin/env -i $(cat cron-env) echo $PATH, sustituye $PATHdesde el shell de llamada; en su lugar, debería invocar una subshell para sustituir en la subenvironement, por ejemplo /usr/bin/env -i $(cat cron-env) /bin/sh -c 'echo $PATH'. 3. Has cometido el mismo error, sustituyendo nuevamente en el shell de llamada en lugar de en el subambiente
John Freeman
41

Les presento una solución basada en la respuesta de Pistos, pero sin los defectos.

  • Agregue la siguiente línea al crontab, por ejemplo, usando crontab -e

    * * * * *  /usr/bin/env > /home/username/cron-env
    
  • Cree un script de shell que ejecute un comando en el mismo entorno en el que se ejecutan los trabajos cron:

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

Utilizar:

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

p.ej

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

Tenga en cuenta que el segundo argumento necesita ser citado si requiere un argumento. La primera línea del script carga un shell POSIX como intérprete. La segunda línea obtiene el archivo de entorno cron. Esto es necesario para cargar el shell correcto, que se almacena en la variable de entorno SHELL. Luego carga un entorno vacío (para evitar fugas de variables de entorno en el nuevo shell), inicia el mismo shell que se utiliza para cronjobs y carga las variables de entorno cron. Finalmente se ejecuta el comando.

Marco
fuente
Esto me ayudó a reproducir mi error de carga de esfinge relacionado con Ruby.
cweiske
1
Usé la opción @reboot cron para escribir el archivo cron-env. Luego puede dejarlo en el crontab y solo se reescribirá cuando se inicie el sistema. Lo hace un poco más simple ya que no tiene que agregar / eliminar líneas.
Michael Barton
Sí, la solución Pistos no funcionó para mí, pero esto funcionó
Stack Underflow
19

Como crontab no hace el trabajo, manipularás su contenido:

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

Que hace :

  • enumera los trabajos de crontab
  • eliminar líneas de comentarios
  • eliminar la configuración de crontab
  • luego lanzarlos uno por uno
Django Janny
fuente
55
Sin embargo, esto no necesariamente lo hace en el mismo entorno que cron, y pensé que quería probar solo uno de ellos.
Falcon Momot
2
correcto, me he equivocado ... ¡Solo ejecuta los trabajos pero no como lo haría cron!
Django Janny
55
sigue siendo una solución increíble +1
Eric Uldall
1
Solo puede sudo -H -u otheruser bash -c 'crontab..." ejecutar el crontab de otro usuario por cierto
Freedo
5

Por defecto, con la mayoría de los demonios cron predeterminados que he visto, simplemente no hay forma de decirle a cron que se ejecute aquí ahora mismo. Si está utilizando anacron, puede ser posible, creo, ejecutar una instancia separada en primer plano.

Si sus scripts no se ejecutan correctamente, entonces no está teniendo en cuenta que

  • el script se ejecuta como un usuario particular
  • cron tiene un entorno restringido (la manifestación más obvia de esto es una ruta diferente).

Desde crontab (5):

El demonio cron (8) configura automáticamente varias variables de entorno. SHELL se establece en / bin / sh, y LOGNAME y HOME se configuran desde la línea / etc / passwd del propietario del crontab. PATH se establece en "/ usr / bin: / bin". HOME, SHELL y PATH pueden ser anulados por la configuración en el crontab; LOGNAME es el usuario desde el que se ejecuta el trabajo y no se puede cambiar.

En general, la RUTA es el mayor problema, por lo que debe:

  • Establezca explícitamente la RUTA dentro del script, durante la prueba, en / usr / bin: / bin. Puede hacer esto en bash con export PATH = "/ usr / bin: / bin"
  • Establezca explícitamente la RUTA adecuada que desea en la parte superior del crontab. por ejemplo, RUTA = "/ usr / bin: / bin: / usr / local / bin: / usr / sbin: / sbin"

Si necesita ejecutar el script como otro usuario sin shell (por ejemplo, www-data), use sudo:

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

Lo primero que debe probar antes de todo eso, por supuesto, es que su script realmente hace lo que se supone que debe hacer desde la línea de comandos. Si no puede ejecutarlo desde la línea de comandos, obviamente no funcionará con cron.

Philip Reynolds
fuente
Gracias por la respuesta completa. Soy consciente de los dos problemas de ejecutar como un usuario particular y con un entorno particular. Como tal, he formulado mi propia respuesta, que ahora publicaré ...
Pistos el
Los caracteres de escape son razones válidas para que el trabajo no se ejecute
Joe Phillips
2

El guión de Marco no me funcionó por alguna razón. No tuve tiempo de depurar, así que escribí un script de Python que hace lo mismo. Es más largo, pero: primero, funciona para mí, y segundo, me resulta más fácil de entender. Cambie "/ tmp / cron-env" donde guardó su entorno. Aquí está:

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

Bueno, el usuario es el mismo que pones en la entrada crontab (o en el crontab en el que lo pones, alternativamente), por lo que es obvio. crontab(5) debería darle la lista de variables de entorno establecidas, solo hay unas pocas.

womble
fuente
En otras palabras, ¿estás diciendo que no hay forma de hacerlo? ¿Solo soluciones alternativas "lo suficientemente cercanas"?
Pistos el
No, digo que puedes hacerlo, usando la información que proporcioné en mi respuesta.
womble
1

En la mayoría de los crontabs como, por ejemplo, vixie-cron, puede colocar variables en el crontab en sí de esta manera y luego usar / usr / bin / env para verificar si funcionó. De esta manera, puede hacer que su script funcione en crontab una vez que descubra cuál es el problema con el script run-as-cron.

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

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

La solución de Marco no funcionó para mí, pero el script de Python de Noam funcionó. Aquí hay una ligera modificación en el guión de Marco que lo hizo funcionar para mí:

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

Las set -avariables de exportación agregadas definidas en el script $ 1 y lo pusieron a disposición para ordenar $ 2

ps La pitón de Noam funcionó porque 'exportó' el entorno al proceso hijo.

Cuenta
fuente
1

Si se trata de un script de shell, esto debería ayudarlo la mayor parte del camino:

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>

Definitivamente resaltará algunos problemas, si no todo.

Matthew Wilcoxson
fuente
0

Nunca he encontrado una manera de ejecutar trabajos cron manualmente, pero este artículo sugiere configurar el mismo entorno que tendría el cronjob y ejecutar el script manualmente.

oneodd1
fuente
¿No es lo que sugiere hacer lo que el OP quiere saber cómo hacer?
womble
Por eso incluí el enlace a la redacción que describe cómo hacerlo. No pensé que fuera necesario copiar y pegar todo aquí.
oneodd1
0

puedes programar el trabajo para comenzar el siguiente minuto :)

AdrP
fuente
77
59 segundos es mucho tiempo.
Stéphane Bruckert
El OP mencionó esta posibilidad en la pregunta: "¿Hay alguna manera de hacer esto? Tener que esperar 60 segundos para probar los cambios en el script no es práctico".
Andrew Grimm
Probablemente, 59 segundos es menos de lo que tomaría seleccionar e implementar cualquiera de las otras soluciones propuestas (y no garantizadas para funcionar). Cuando veo tales deficiencias, me pregunto cómo Linux se convirtió en un sistema operativo de servidor estándar de facto. ¿No querría un administrador de sistemas serio probar sus trabajos?
Rolf
0

Dije a la respuesta de Marco. El código se muestra a continuación, pero mantendré este script aquí .

Dado este 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"

Sesión de uso de muestra:

$ 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

Esto es cronTest2, que debe invocarse correctamente para configurar las variables de entorno de la misma manera que lo hace 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" 

cronTestse ejecuta cronTest2con las variables de entorno adecuadas establecidas:

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