Executando um trabalho cron manualmente e imediatamente

108

(Eu já li Como posso testar um novo script cron?. )

Eu tenho um problema específico (o trabalho do cron não parece ser executado ou é executado corretamente), mas o problema é geral: eu gostaria de depurar scripts que são programados. Estou ciente de que posso configurar uma linha * * * * * crontab, mas essa não é uma solução totalmente satisfatória. Eu gostaria de poder executar um trabalho cron a partir da linha de comando, como se o cron estivesse executando (mesmo usuário, mesmas variáveis ​​de ambiente etc.). Existe uma maneira de fazer isso? Ter que esperar 60 segundos para testar as alterações de script não é prático.

Pistos
fonte
(desculpe, não é possível adicionar comentário) 0 30 16 20 *? * Mesmo se você executar o trabalho como esse, toda a idéia é fornecer saída do script para ver o que está acontecendo de errado a não ser que o trabalho grava em um log, este é quuiet inútil

Respostas:

80

Aqui está o que eu fiz, e parece funcionar nessa situação. Pelo menos, ele me mostra um erro, enquanto a execução na linha de comando, pois o usuário não mostra o erro.


Etapa 1 : coloquei essa linha temporariamente no crontab do usuário:

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

depois retirou-o assim que o arquivo foi gravado.

Etapa 2 : Criei um pequeno script bash run-as-cron contendo:

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

Então, como usuário em questão, eu pude

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

Obviamente, essa solução poderia ser expandida para usar o sudo ou algo semelhante para obter mais flexibilidade.

Espero que isso ajude os outros.

Pistos
fonte
8
Isso não funciona para mim e eu me pergunto se funciona para alguém que votou. 1) Por que você está usando o bash? Não é necessário aqui e pode não estar localizado /usr/bin. 2) A cat …/cron-envsaída de várias linhas, ou seja, não funciona. Apenas tente executar /usr/bin/env -i $(cat cron-env) echo $PATHno terminal, ele gera o ambiente literalmente em vez de usá-lo. 3) O ambiente atual vaza no ambiente cron emulado. Tente: export foo=leaked; run-as-cron echo $foo.
Marco Marco
O @Marco trabalha no bash, que é o que eu uso, pois é um ambiente melhor definido do que o sh. Eu uso tudo, desde pdksh, ksh (várias versões), bash e dash, por isso estou muito ciente das diferenças entre implementações de "puro" de sh, mesmo quando permanecendo estritamente no subconjunto comum das linguagens. :-)
Max Murphy
7
O @Marco 2. catgera várias linhas, que funcionam, porque a substituição do shell as reduz em uma única linha, com a qual você pode verificar echo $(cat cron-env ) | wc; seu comando de exemplo /usr/bin/env -i $(cat cron-env) echo $PATHsubstitui $PATHo shell de chamada; em vez disso, deve invocar um subshell para substituir no subambiente, por exemplo /usr/bin/env -i $(cat cron-env) /bin/sh -c 'echo $PATH'. 3. Você fez o mesmo erro, novamente substituindo no shell chamado em vez de no subambiente
John Freeman
41

Apresento uma solução baseada na resposta de Pistos, mas sem falhas.

  • Adicione a seguinte linha ao crontab, por exemplo, usando crontab -e

    * * * * *  /usr/bin/env > /home/username/cron-env
    
  • Crie um script de shell que execute um comando no mesmo ambiente em que as tarefas cron são executadas:

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

Usar:

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

por exemplo

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

Observe que o segundo argumento precisa ser citado se precisar de um argumento. A primeira linha do script carrega um shell POSIX como intérprete. A segunda linha origina o arquivo do ambiente cron. Isso é necessário para carregar o shell correto, que é armazenado na variável de ambiente SHELL. Em seguida, ele carrega um ambiente vazio (para evitar o vazamento de variáveis ​​de ambiente no novo shell), inicia o mesmo shell usado para cronjobs e carrega as variáveis ​​de ambiente cron. Finalmente, o comando é executado.

Marco
fonte
isso me ajudou a reproduzir meu erro de carregamento de esfinge relacionado ao ruby.
Cwiske
1
Eu usei a opção cron @reboot para escrever o arquivo cron-env. Você pode então deixá-lo no crontab e ele só será reescrito quando o sistema for iniciado. Isso torna um pouco mais simples, pois você não precisa adicionar / remover linhas.
Michael Barton
Sim, a solução Pistos não funcionou para mim, mas isso funcionou.
Stack Underflow
19

Como o crontab não faz o trabalho, você manipula seu conteúdo:

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

O que faz :

  • lista crontab trabalhos
  • remover linhas de comentário
  • remova a configuração do crontab
  • então lance-os um por um
Django Janny
fonte
5
Isso não necessariamente o faz no mesmo ambiente que o cron faria, e eu pensei que ele queria testar apenas um deles.
Falcon Momot
2
correto, eu me enganei ... Ele apenas executava os trabalhos, mas não como o cron faria!
quer
5
ainda uma solução incrível +1
Eric Uldall
1
Você pode simplesmente sudo -H -u otheruser bash -c 'crontab..." executar o crontab de outro usuário btw
Freedo 14/09
5

Por padrão, com a maioria dos daemons padrão do cron que eu já vi, simplesmente não há como dizer ao cron para rodar aqui agora. Se você estiver usando o anacron, acho que é possível executar uma instância separada em primeiro plano.

Se seus scripts não estiverem sendo executados corretamente, você não levará em consideração que

  • o script está sendo executado como um usuário específico
  • O cron possui um ambiente restrito (a manifestação mais óbvia disso é um caminho diferente).

Do crontab (5):

Várias variáveis ​​de ambiente são configuradas automaticamente pelo daemon cron (8). SHELL está definido como / bin / sh, e LOGNAME e HOME são definidos na linha / etc / passwd do proprietário do crontab. PATH está definido como "/ usr / bin: / bin". HOME, SHELL e PATH podem ser substituídos pelas configurações no crontab; LOGNAME é o usuário do qual o trabalho está sendo executado e não pode ser alterado.

Em geral, PATH é o maior problema, então você precisa:

  • Defina explicitamente o PATH no script, durante o teste, como / usr / bin: / bin. Você pode fazer isso no bash com a exportação PATH = "/ usr / bin: / bin"
  • Defina explicitamente o CAMINHO adequado que você deseja na parte superior do crontab. por exemplo, PATH = "/ usr / bin: / bin: / usr / local / bin: / usr / sbin: / sbin"

Se você precisar executar o script como outro usuário sem um shell (por exemplo, www-data), use sudo:

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

A primeira coisa a testar antes de tudo isso, é claro, é que seu script realmente faz o que deve fazer na linha de comando. Se você não pode executá-lo na linha de comando, obviamente não funcionará com o cron.

Philip Reynolds
fonte
Obrigado pela resposta completa. Estou ciente dos dois problemas de execução como um usuário específico e com um ambiente específico. Como tal, eu formulado minha própria resposta, que agora vai postar ...
Pistos
Caracteres de escape estão razões válidas para o trabalho não correr
Joe Phillips
2

O roteiro de Marco não funcionou para mim por algum motivo. Como não tive tempo de depurar, escrevi um script Python que faz a mesma coisa. É mais longo, mas: primeiro, funciona para mim e, segundo, acho mais fácil entender. Altere "/ tmp / cron-env" para onde você salvou seu ambiente. Aqui 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
fonte
1

Bem, o usuário é o mesmo que você inseriu na entrada do crontab (ou em qual crontab você o inseriu, alternadamente), então isso é óbvio. crontab(5) deve fornecer a lista de variáveis ​​de ambiente definidas, existem apenas algumas.

mulher
fonte
Em outras palavras, você está dizendo que não há como fazê-lo? Apenas soluções "fechadas o suficiente"?
Pistos
Não, estou dizendo que você pode fazer isso usando as informações que forneci na minha resposta.
Womble
1

Na maioria dos crontabs como, por exemplo, vixie-cron, você pode colocar variáveis ​​no próprio crontab assim e usar / usr / bin / env para verificar se funcionou. Dessa forma, você pode fazer seu script funcionar no crontab depois de descobrir o que há de errado com o script run-as-cron.

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

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

A solução de Marco não funcionou para mim, mas o script python de Noam funcionou. Aqui está uma pequena modificação no script de Marco que o fez funcionar para mim:

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

As set -avariáveis ​​de exportação adicionadas definidas no script $ 1 e disponibilizadas para o comando $ 2

O python do ps Noam funcionou porque ele 'exportou' o ambiente para o processo filho.

Conta
fonte
1

Se é um script de shell, isso deve levá-lo ao longo do caminho:

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, destacará alguns problemas, se não tudo.

Matthew Wilcoxson
fonte
0

Eu nunca encontrei uma maneira de executar tarefas cron manualmente, mas esse artigo sugere definir o mesmo ambiente que o cronjob teria e executar o script manualmente.

oneodd1
fonte
Não é o que você sugere fazer o que o OP quer saber como fazer?
Womble
É por isso que incluí o link do artigo que descreve como fazê-lo. Não achei necessário copiar e colar tudo aqui.
oneodd1
0

você pode programar o trabalho para começar no próximo minuto :)

AdrP
fonte
7
59 segundos é muito tempo.
Stéphane Bruckert 4/11
O OP mencionou essa possibilidade na pergunta: "Existe uma maneira de fazer isso? Ter que esperar 60 segundos para testar as alterações de script não é prático".
Andrew Grimm
59 segundos é provavelmente menos do que seria necessário para escolher e implementar qualquer outra solução proposta (e não garantida que funcione). Quando vejo essas deficiências, imagino como o Linux se tornou um sistema operacional padrão de servidor. Nenhum administrador de sistemas sério não gostaria de testar seus trabalhos?
Rolf
0

Eu mexi na resposta de Marco. O código é mostrado abaixo, mas vou manter esse script aqui .

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"

Sessão de uso de amostra:

$ 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

Isto é cronTest2, que precisa ser chamado corretamente para configurar as variáveis ​​de ambiente da mesma maneira que o 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é executado cronTest2com o conjunto de variáveis ​​de ambiente apropriado:

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