Esecuzione di un processo cron manualmente e immediatamente


108

(Ho già letto Come posso testare un nuovo cron script?. )

Ho un problema specifico (cron job non sembra funzionare o funziona correttamente), ma il problema è generale: vorrei eseguire il debug degli script che sono bloccati. Sono consapevole di poter impostare una linea crontab * * * * *, ma questa non è una soluzione pienamente soddisfacente. Vorrei poter eseguire un cron job dalla riga di comando come se cron lo stesse eseguendo (stesso utente, stesse variabili di ambiente, ecc.). C'è un modo per fare questo? Dover attendere 60 secondi per testare le modifiche allo script non è pratico.


(scusa non posso aggiungere un commento) 0 30 16 20 *? * anche se si esegue il lavoro in questo modo, l'idea è di fornire l'output dello script per vedere cosa non va a meno che il lavoro non scriva in un registro, questo è assolutamente inutile

Risposte:


80

Ecco cosa ho fatto e sembra funzionare in questa situazione. Almeno, mi mostra un errore, mentre in esecuzione dalla riga di comando come l'utente non mostra l'errore.


Passaggio 1 : inserisco temporaneamente questa riga nel crontab dell'utente:

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

poi l'ha tolto una volta che il file è stato scritto.

Step 2 : Mi sono fatto un piccolo script bash run-as-cron contenente:

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

Quindi, come utente in questione, sono stato in grado di farlo

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

Questa soluzione potrebbe ovviamente essere ampliata per utilizzare sudo o simili per una maggiore flessibilità.

Spero che questo aiuti gli altri.


8
Questo non funziona per me e mi chiedo se lo sia per chiunque abbia votato. 1) Perché stai usando bash? Non è richiesto qui e potrebbe non trovarsi in /usr/bin. 2) Le cat …/cron-envlinee multiple di output, che non funziona. Basta provare a eseguirlo /usr/bin/env -i $(cat cron-env) echo $PATHnel terminale, genera letteralmente l'ambiente invece di usarlo. 3) L'ambiente corrente perde nell'ambiente cron emulato. Prova: export foo=leaked; run-as-cron echo $foo.
Marco,

@Marco Funziona in bash, che è quello che uso in quanto è un ambiente meglio definito di sh. Uso tutto, da pdksh, ksh (versioni multiple), bash e dash, quindi sono molto consapevole delle differenze tra le implementazioni di "pure" di sh, anche se rimango molto rigorosamente nel sottoinsieme comune delle lingue. :-)
Max Murphy

7
@Marco 2. catgenera più linee, che funzionano, perché la sostituzione della shell le fa collassare in una singola linea, con cui puoi verificare echo $(cat cron-env ) | wc; il comando di esempio /usr/bin/env -i $(cat cron-env) echo $PATH, sostituisce $PATHdalla shell chiamante; invece, dovrebbe invocare una subshell da sostituire nel sottosviluppo, ad es /usr/bin/env -i $(cat cron-env) /bin/sh -c 'echo $PATH'. 3. Hai fatto lo stesso errore, sostituendo di nuovo nella shell chiamante invece che nel subambiente
John Freeman,

41

Vi presento una soluzione basata sulla risposta di Pistos, ma senza i difetti.

  • Aggiungi la seguente riga al crontab, ad es. Usando crontab -e

    * * * * *  /usr/bin/env > /home/username/cron-env
    
  • Creare uno script di shell che esegue un comando nello stesso ambiente in cui vengono eseguiti cron job:

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

Uso:

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

per esempio

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

Si noti che il secondo argomento deve essere citato se richiede un argomento. La prima riga dello script carica una shell POSIX come interprete. La seconda riga genera il file di ambiente cron. Questo è necessario per caricare la shell corretta, che è memorizzata nella variabile d'ambiente SHELL. Quindi carica un ambiente vuoto (per evitare perdite di variabili d'ambiente nella nuova shell), avvia la stessa shell utilizzata per cronjobs e carica le variabili di ambiente cron. Alla fine il comando viene eseguito.


questo mi ha aiutato a riprodurre il mio errore di caricamento della sfinge legato al rubino.
cweiske,

1
Ho usato l'opzione cron @reboot per scrivere il file cron-env. È quindi possibile lasciarlo nel crontab e verrà riscritto solo all'avvio del sistema. Lo rende un po 'più semplice poiché non è necessario aggiungere / rimuovere linee.
Michael Barton,

Sì, la soluzione Pistos non ha funzionato per me, ma questo ha funzionato
Stack Underflow,

19

Poiché crontab non fa il lavoro, dovrai manipolarne il contenuto:

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

Cosa fa :

  • elenca i lavori crontab
  • rimuovere le righe di commento
  • rimuovere la configurazione di crontab
  • quindi lanciarli uno per uno

5
Questo non lo fa necessariamente nello stesso ambiente di cron, però, e ho pensato che volesse testarne solo uno.
Falcon Momot,

2
corretto, mi sono sbagliato ... Esegue solo i lavori ma non come farebbe cron!
Django Janny,

5
ancora una soluzione fantastica +1
Eric Uldall,

1
Puoi semplicemente sudo -H -u otheruser bash -c 'crontab..." eseguire il crontab di un altro utente tra cui
Freedo

5

Di default con la maggior parte dei demoni cron predefiniti che ho visto, semplicemente non c'è modo di dire a cron di funzionare proprio ora. Se stai usando anacron, potrebbe essere possibile, penso, eseguire un'istanza separata in primo piano.

Se i tuoi script non funzionano correttamente, non lo stai prendendo in considerazione

  • lo script è in esecuzione come un determinato utente
  • cron ha un ambiente limitato (la manifestazione più ovvia di questo è un percorso diverso).

Da crontab (5):

Diverse variabili di ambiente vengono impostate automaticamente dal demone cron (8). SHELL è impostato su / bin / sh e LOGNAME e HOME sono impostati dalla riga / etc / passwd del proprietario del crontab. PATH è impostato su "/ usr / bin: / bin". HOME, SHELL e PATH possono essere sovrascritti dalle impostazioni nel crontab; LOGNAME è l'utente da cui è in esecuzione il lavoro e non può essere modificato.

In generale, PATH è il problema più grande, quindi è necessario:

  • Impostare esplicitamente il PERCORSO all'interno dello script, durante il test, su / usr / bin: / bin. Puoi farlo in bash con export PATH = "/ usr / bin: / bin"
  • Imposta esplicitamente il PERCORSO corretto che desideri in cima al crontab. ad es. PATH = "/ usr / bin: / bin: / usr / local / bin: / usr / sbin: / sbin"

Se è necessario eseguire lo script come un altro utente senza shell (ad es. Www-data), utilizzare sudo:

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

La prima cosa da testare prima di tutto, ovviamente, è che il tuo script in realtà fa quello che dovrebbe fare dalla riga di comando. Se non puoi eseguirlo dalla riga di comando, ovviamente non funzionerà con cron.


Grazie per la risposta approfondita. Sono a conoscenza dei due problemi di esecuzione come un utente particolare e con un ambiente particolare. Come tale, ho formulato la mia risposta, che ora
posterò

I personaggi di escape sono validi motivi per cui il lavoro non viene eseguito
Joe Phillips,

2

La sceneggiatura di Marco non ha funzionato per me per qualche motivo. Non ho avuto il tempo di eseguire il debug, quindi ho scritto uno script Python che fa la stessa cosa. È più lungo, ma: in primo luogo, funziona per me, e in secondo luogo, lo trovo più facile da capire. Cambia "/ tmp / cron-env" nel punto in cui hai salvato il tuo ambiente. Ecco qui:

#!/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()

1

Bene, l'utente è uguale a quello che hai inserito nella voce crontab (o il cui crontab lo metti, alternativamente), quindi è un gioco da ragazzi. crontab(5) dovrebbe darti l'elenco delle variabili d'ambiente impostate, ce ne sono solo alcune.


In altre parole, stai dicendo che non c'è modo di farlo? Solo soluzioni alternative "abbastanza vicine"?
Pistos,

No, sto dicendo che puoi farlo, usando le informazioni che ho fornito nella mia risposta.
Womble

1

Nella maggior parte dei crontab come ad es. Vixie-cron puoi posizionare le variabili nel crontab stesso in questo modo e quindi usare / usr / bin / env per verificare se ha funzionato. In questo modo puoi far funzionare il tuo script in crontab dopo aver scoperto che cosa non va nello script run-as-cron.

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

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

1

La soluzione di Marco non ha funzionato per me, ma la sceneggiatura in pitone di Noam ha funzionato. Ecco una leggera modifica alla sceneggiatura di Marco che l'ha fatto funzionare per me:

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

Le set -avariabili di esportazione aggiunte definite nello script $ 1 e rese disponibili al comando $ 2

ps Il pitone di Noam ha funzionato perché ha "esportato" l'ambiente nel processo figlio.


1

Se si tratta di uno script di shell, questo dovrebbe aiutarti molto:

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>

Evidenzierà sicuramente alcuni problemi, se non tutto.


0

Non ho mai trovato il modo di eseguire i lavori cron manualmente, ma questo articolo suggerisce di impostare lo stesso ambiente del cronjob e di eseguire manualmente lo script.


Ciò che suggerisci di fare non è ciò che l'OP vuole sapere come fare?
Womble

Quale sarebbe il motivo per cui ho incluso il link per la scrittura che descrive come farlo. Non pensavo fosse necessario copiare e incollare tutto qui.
oneodd1

0

puoi programmare il lavoro per iniziare il minuto successivo :)


7
59 secondi sono un sacco di tempo.
Stéphane Bruckert,

L'OP ha menzionato questa possibilità nella domanda: "C'è un modo per farlo? Dover aspettare 60 secondi per testare le modifiche agli script non è pratico."
Andrew Grimm,

59 secondi è probabilmente inferiore a quello che sarebbe necessario per scegliere e implementare qualsiasi altra soluzione proposta (e non garantita per funzionare). Quando vedo tali carenze, mi chiedo come Linux sia diventato un sistema operativo server di fatto standard. Nessun amministratore di sistema serio vorrebbe testare il proprio lavoro?
Rolf,

0

Ho cercato la risposta di Marco. Il codice è mostrato sotto, ma manterrò questo script qui .

Dato questo 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"

Esempio di sessione di utilizzo:

$ 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

Questo è cronTest2, che deve essere correttamente invocato per impostare le variabili di ambiente allo stesso modo di 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" 

cronTestfunziona cronTest2con le variabili d'ambiente appropriate impostate:

#!/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"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.