Menjalankan pekerjaan cron secara manual dan segera

108

(Saya sudah membaca Bagaimana saya bisa menguji skrip cron baru? )

Saya memiliki masalah khusus (pekerjaan cron tampaknya tidak berjalan, atau berjalan dengan benar), tetapi masalah ini bersifat umum: Saya ingin men-debug skrip yang disinkronkan. Saya sadar bahwa saya dapat mengatur baris * * * * * crontab, tetapi itu bukan solusi yang sepenuhnya memuaskan. Saya ingin dapat menjalankan pekerjaan cron dari baris perintah seolah-olah cron menjalankannya (pengguna yang sama, variabel lingkungan yang sama, dll.). Apakah ada cara untuk melakukan ini? Harus menunggu 60 detik untuk menguji perubahan skrip tidak praktis.

Pistos
sumber
(maaf tidak bisa menambahkan komentar) 0 30 16 20 *? * bahkan jika Anda menjalankan pekerjaan seperti itu, seluruh idenya adalah untuk memberikan hasil skrip untuk melihat apa yang salah kecuali pekerjaan menulis ke log, ini tidak berguna

Jawaban:

80

Inilah yang saya lakukan, dan sepertinya berhasil dalam situasi ini. Setidaknya, ini menunjukkan saya kesalahan, sedangkan berjalan dari baris perintah karena pengguna tidak menunjukkan kesalahan.


Langkah 1 : Saya meletakkan baris ini sementara di crontab pengguna:

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

kemudian mengeluarkannya begitu file itu ditulis.

Langkah 2 : Buat sendiri skrip bash run-as-cron kecil yang berisi:

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

Jadi, sebagai pengguna yang dimaksud, saya bisa

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

Solusi ini jelas dapat diperluas untuk memanfaatkan sudo atau semacamnya agar lebih fleksibel.

Semoga ini bisa membantu orang lain.

Pistos
sumber
8
Ini tidak bekerja untuk saya dan saya bertanya-tanya apakah itu berlaku untuk siapa pun yang terunggul. 1) Mengapa Anda menggunakan bash? Tidak diperlukan di sini dan mungkin tidak berlokasi di /usr/bin. 2) cat …/cron-envOutput beberapa baris, yang tidak berfungsi. Coba saja jalankan /usr/bin/env -i $(cat cron-env) echo $PATHdi terminal, ini menghasilkan lingkungan secara harfiah daripada menggunakannya. 3) Lingkungan saat ini bocor ke dalam lingkungan cron yang ditiru. Cobalah: export foo=leaked; run-as-cron echo $foo.
Marco
@ Mars Bekerja di bash, yang saya gunakan karena ini adalah lingkungan yang lebih baik daripada sh. Saya menggunakan semuanya dari pdksh, ksh (beberapa versi), bash dan dash jadi saya sangat menyadari perbedaan antara implementasi "pure" of sh, bahkan ketika tetap sangat ketat di subset umum bahasa. :-)
Max Murphy
7
@ Mars 2. catoutput beberapa baris, yang berfungsi, karena penggantian shell runtuh menjadi satu baris, yang dapat Anda periksa dengan echo $(cat cron-env ) | wc; contoh perintah Anda /usr/bin/env -i $(cat cron-env) echo $PATH,, pengganti $PATHdari shell panggilan; sebagai gantinya, harus meminta subkulit untuk menggantikan dalam sub-lingkungan, misalnya /usr/bin/env -i $(cat cron-env) /bin/sh -c 'echo $PATH'. 3. Anda telah melakukan kesalahan yang sama, lagi-lagi mengganti kulit panggilan alih-alih di lingkungan
John Freeman
41

Saya menyajikan solusi berdasarkan jawaban Pistos, tetapi tanpa cacat.

  • Tambahkan baris berikut ke crontab, misalnya menggunakan crontab -e

    * * * * *  /usr/bin/env > /home/username/cron-env
    
  • Buat skrip shell yang menjalankan perintah di lingkungan yang sama dengan cron jobs dijalankan:

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

Menggunakan:

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

misalnya

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

Perhatikan bahwa argumen kedua perlu dikutip jika memerlukan argumen. Baris pertama skrip memuat shell POSIX sebagai juru bahasa. Baris kedua sumber file lingkungan cron. Ini diperlukan untuk memuat shell yang benar, yang disimpan dalam variabel lingkungan SHELL. Kemudian memuat lingkungan kosong (untuk mencegah bocornya variabel lingkungan ke dalam shell baru), meluncurkan shell yang sama yang digunakan untuk cronjobs dan memuat variabel lingkungan cron. Akhirnya perintah dieksekusi.

Marco
sumber
ini membantu saya mereproduksi kesalahan pemuatan sphinx terkait ruby ​​saya.
cweiske
1
Saya menggunakan opsi cron @reboot untuk menulis file cron-env. Anda kemudian dapat meninggalkannya di crontab dan hanya akan ditulis ulang ketika sistem dimulai. Itu membuatnya sedikit lebih sederhana karena Anda tidak perlu menambah / menghapus baris.
Michael Barton
Ya, solusi Pistos tidak bekerja untuk saya tetapi ini berhasil
Stack Underflow
19

Karena crontab tidak melakukan pekerjaannya, Anda akan memanipulasi kontennya:

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

Apa fungsinya:

  • daftar pekerjaan crontab
  • hapus baris komentar
  • hapus konfigurasi crontab
  • kemudian luncurkan satu per satu
Django Janny
sumber
5
Namun, ini tidak harus dilakukan di lingkungan yang sama dengan cron, dan saya pikir dia hanya ingin menguji salah satunya.
Falcon Momot
2
benar, saya telah salah ... Itu hanya menjalankan pekerjaan tetapi tidak seperti yang akan dilakukan cron!
Django Janny
5
masih merupakan solusi yang luar biasa +1
Eric Uldall
1
Anda bisa sudo -H -u otheruser bash -c 'crontab..." menjalankan crontab pengguna lain btw
Freedo
5

Secara default dengan sebagian besar daemon cron default yang saya lihat, tidak ada cara untuk memberitahu cron untuk berjalan di sini sekarang. Jika Anda menggunakan anacron, mungkin saya pikir menjalankan contoh terpisah di latar depan.

Jika skrip Anda tidak berjalan dengan benar maka Anda tidak memperhitungkannya

  • skrip berjalan sebagai pengguna tertentu
  • cron memiliki lingkungan terbatas (manifestasi paling jelas dari ini adalah jalan yang berbeda).

Dari crontab (5):

Beberapa variabel lingkungan diatur secara otomatis oleh daemon cron (8). SHELL diatur ke / bin / sh, dan LOGNAME dan HOME diatur dari baris / etc / passwd dari pemilik crontab. PATH diatur ke "/ usr / bin: / bin". HOME, SHELL, dan PATH mungkin ditimpa oleh pengaturan di crontab; LOGNAME adalah pengguna darimana pekerjaan itu berjalan, dan tidak dapat diubah.

Secara umum PATH adalah masalah terbesar, jadi Anda perlu:

  • Secara eksplisit mengatur PATH dalam skrip, saat pengujian, ke / usr / bin: / bin. Anda dapat melakukan ini di bash dengan ekspor PATH = "/ usr / bin: / bin"
  • Secara eksplisit mengatur PATH yang tepat yang Anda inginkan di bagian atas crontab. misalnya PATH = "/ usr / bin: / bin: / usr / local / bin: / usr / sbin: / sbin"

Jika Anda perlu menjalankan skrip sebagai pengguna lain tanpa shell (mis. Www-data), gunakan sudo:

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

Hal pertama yang harus diuji sebelum semua itu, tentu saja, adalah skrip Anda benar-benar melakukan apa yang seharusnya dilakukan dari baris perintah. Jika Anda tidak dapat menjalankannya dari baris perintah, itu jelas tidak akan bekerja dengan cron.

Philip Reynolds
sumber
Terima kasih atas tanggapannya. Saya menyadari dua masalah berjalan sebagai pengguna tertentu, dan dengan lingkungan tertentu. Karena itu, saya sudah dirumuskan jawaban saya sendiri, yang sekarang saya akan posting ...
pistos
Karakter melarikan diri adalah alasan yang valid untuk pekerjaan yang tidak berjalan
Joe Phillips
2

Naskah Marco tidak bekerja untuk saya karena suatu alasan. Saya tidak punya waktu untuk debug, jadi saya menulis skrip Python yang melakukan hal yang sama. Itu lebih lama, tetapi: pertama, itu bekerja untuk saya, dan kedua, saya merasa lebih mudah untuk dipahami. Ubah "/ tmp / cron-env" ke tempat Anda menyimpan lingkungan Anda. Ini dia:

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

Nah, pengguna sama dengan yang Anda masukkan ke entri crontab (atau crontab yang Anda masukkan ke dalamnya, secara bergantian), jadi itu adalah no-brainer. crontab(5) harus memberi Anda daftar variabel lingkungan yang ditetapkan, hanya ada beberapa.

womble
sumber
Dengan kata lain, Anda mengatakan tidak ada cara untuk melakukannya? Hanya solusi "cukup dekat"?
Pistos
Tidak, saya katakan Anda bisa melakukannya, menggunakan informasi yang saya berikan dalam jawaban saya.
womble
1

Pada kebanyakan crontab seperti misalnya vixie-cron, Anda dapat menempatkan variabel di crontab itu sendiri seperti ini dan kemudian menggunakan / usr / bin / env untuk memeriksa apakah itu berfungsi. Dengan cara ini Anda dapat membuat skrip Anda berfungsi di crontab setelah Anda menemukan apa yang salah dengan skrip run-as-cron.

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

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

Solusi Marco tidak berhasil untuk saya tetapi skrip python Noam berhasil. Berikut sedikit modifikasi pada skrip Marco yang membuatnya berfungsi untuk saya:

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

set -aVariabel ekspor yang ditambahkan didefinisikan dalam skrip $ 1 dan membuatnya tersedia untuk memerintahkan $ 2

ps Noam's python bekerja karena 'mengekspor' lingkungan ke proses anak.

Tagihan
sumber
1

Jika ini adalah skrip shell, ini akan membantu Anda:

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>

Ini pasti akan menyoroti beberapa masalah, jika tidak semuanya.

Matthew Wilcoxson
sumber
0

Saya tidak pernah menemukan cara untuk menjalankan pekerjaan cron secara manual tetapi artikel ini menyarankan pengaturan lingkungan yang sama dengan yang dimiliki cronjob dan menjalankan skrip secara manual.

oneodd1
sumber
Bukankah apa yang Anda sarankan untuk melakukan apa yang OP ingin tahu bagaimana melakukannya?
womble
Itulah sebabnya saya memasukkan tautan ke artikel yang menjelaskan cara melakukannya. Saya pikir tidak perlu menyalin-menempelkan semuanya di sini.
oneodd1
0

Anda dapat memprogram pekerjaan untuk memulai menit berikutnya :)

AdrP
sumber
7
59 detik adalah banyak waktu.
Stéphane Bruckert
OP menyebutkan kemungkinan ini dalam pertanyaan: "Apakah ada cara untuk melakukan ini? Harus menunggu 60 detik untuk menguji perubahan skrip tidak praktis."
Andrew Grimm
59 detik mungkin kurang dari yang dibutuhkan untuk memilih dan mengimplementasikan solusi yang diusulkan (dan tidak dijamin untuk bekerja). Ketika saya melihat kekurangan seperti itu saya bertanya-tanya bagaimana Linux menjadi OS server standar de-facto. Tidakkah ada sysadmin serius yang ingin menguji pekerjaan mereka?
Rolf
0

Aku mengomentari jawaban Marco. Kode ditampilkan di bawah, tetapi saya akan mempertahankan skrip ini di sini .

Diberikan crontab ini:

# 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 penggunaan sampel:

$ 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

Ini cronTest2, yang perlu dipanggil dengan benar untuk mengatur variabel lingkungan dengan cara yang sama seperti yang dilakukan 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" 

cronTestberjalan cronTest2dengan set variabel lingkungan yang tepat:

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