Chạy một công việc định kỳ bằng tay và ngay lập tức


108

(Tôi đã đọc Làm thế nào tôi có thể kiểm tra tập lệnh cron mới ? .)

Tôi có một vấn đề cụ thể (công việc cron không xuất hiện để chạy hoặc chạy đúng cách), nhưng vấn đề là chung: Tôi muốn gỡ lỗi các tập lệnh được ghi lại. Tôi biết rằng tôi có thể thiết lập một dòng crontab * * * * *, nhưng đó không phải là một giải pháp hoàn toàn thỏa đáng. Tôi muốn có thể chạy một công việc cron từ dòng lệnh như thể cron đang chạy nó (cùng một người dùng, cùng các biến môi trường, v.v.). Có cách nào để làm việc này không? Phải chờ 60 giây để kiểm tra thay đổi tập lệnh là không thực tế.


(xin lỗi không thể thêm nhận xét) 0 30 16 20 *? * ngay cả khi bạn điều hành công việc như vậy, toàn bộ ý tưởng là cung cấp đầu ra tập lệnh để xem có gì sai trừ khi công việc ghi vào nhật ký, điều này là vô dụng

Câu trả lời:


80

Đây là những gì tôi đã làm, và nó dường như hoạt động trong tình huống này. Ít nhất, nó cho tôi thấy một lỗi, trong khi chạy từ dòng lệnh vì người dùng không hiển thị lỗi.


Bước 1 : Tôi tạm thời đặt dòng này vào crontab của người dùng:

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

sau đó lấy nó ra khi tập tin được viết

Bước 2 : Tạo cho mình một tập lệnh bash run-as-cron nhỏ chứa:

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

Vì vậy, như người dùng trong câu hỏi, tôi đã có thể

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

Giải pháp này rõ ràng có thể được mở rộng để sử dụng sudo hoặc như vậy để linh hoạt hơn.

Hy vọng điều này sẽ giúp những người khác.


8
Điều này không làm việc cho tôi và tôi tự hỏi nếu nó làm cho bất cứ ai nâng cao. 1) Tại sao bạn sử dụng bash? Nó không được yêu cầu ở đây và nó có thể không được đặt tại /usr/bin. 2) Các cat …/cron-envđầu ra nhiều dòng, không hoạt động. Chỉ cần cố gắng thực hiện /usr/bin/env -i $(cat cron-env) echo $PATHtrong thiết bị đầu cuối, nó xuất ra môi trường theo nghĩa đen thay vì sử dụng nó. 3) Môi trường hiện tại rò rỉ vào môi trường cron mô phỏng. Hãy thử : export foo=leaked; run-as-cron echo $foo.
Marco

@Marco Hoạt động trong bash, đó là những gì tôi sử dụng vì nó là một môi trường được xác định tốt hơn so với sh. Tôi sử dụng mọi thứ từ pdksh, ksh (nhiều phiên bản), bash và dash vì vậy tôi nhận thức được sự khác biệt giữa việc triển khai "thuần túy" của sh, ngay cả khi ở rất nghiêm ngặt trong tập hợp con của các ngôn ngữ. :-)
Max Murphy

7
@Marco 2. catxuất ra nhiều dòng, hoạt động được, vì sự thay thế shell thu gọn chúng thành một dòng duy nhất mà bạn có thể kiểm tra echo $(cat cron-env ) | wc; lệnh ví dụ của bạn /usr/bin/env -i $(cat cron-env) echo $PATH, thay thế $PATHtừ vỏ gọi; thay vào đó, nó nên gọi một lớp con để thay thế trong môi trường con, ví dụ /usr/bin/env -i $(cat cron-env) /bin/sh -c 'echo $PATH'. 3. Bạn đã mắc một lỗi tương tự, một lần nữa thay thế trong vỏ gọi thay vì trong môi trường con
John Freeman

41

Tôi trình bày một giải pháp dựa trên câu trả lời của Pistos, nhưng không có sai sót.

  • Thêm dòng sau vào crontab, ví dụ: sử dụng crontab -e

    * * * * *  /usr/bin/env > /home/username/cron-env
    
  • Tạo một tập lệnh shell thực thi một lệnh trong cùng môi trường với các lệnh cron chạy:

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

Sử dụng:

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

ví dụ

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

Lưu ý rằng đối số thứ hai cần được trích dẫn nếu nó yêu cầu một đối số. Dòng đầu tiên của tập lệnh tải shell POSIX làm trình thông dịch. Dòng thứ hai nguồn tệp môi trường cron. Điều này là cần thiết để tải shell chính xác, được lưu trữ trong biến môi trường SHELL. Sau đó, nó tải một môi trường trống (để tránh rò rỉ các biến môi trường vào lớp vỏ mới), khởi chạy cùng một vỏ được sử dụng cho cronjobs và tải các biến môi trường cron. Cuối cùng lệnh được thực thi.


điều này giúp tôi tái tạo lỗi tải nhân sư liên quan đến ruby ​​của tôi.
cweiske

1
Tôi đã sử dụng tùy chọn cron @reboot để ghi tệp cron-env. Sau đó, bạn có thể để nó trong crontab và nó sẽ chỉ được viết lại khi hệ thống được khởi động. Nó làm cho nó đơn giản hơn một chút vì bạn không phải thêm / xóa dòng.
Michael Barton

Có, giải pháp Pistos không hiệu quả với tôi nhưng điều này đã làm
Stack Underflow

19

Vì crontab không thực hiện công việc, bạn sẽ thao túng nội dung của nó:

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

Những gì nó làm :

  • liệt kê các công việc crontab
  • xóa các dòng bình luận
  • xóa cấu hình crontab
  • sau đó khởi chạy từng cái một

5
Tuy nhiên, điều này không nhất thiết phải làm điều đó trong cùng một môi trường mà cron sẽ làm và tôi nghĩ anh ta chỉ muốn thử nghiệm một trong số họ.
Falcon Momot

2
đúng, tôi đã nhầm ... Nó chỉ chạy các công việc chứ không như cron sẽ làm!
Django Janny

5
vẫn là một giải pháp tuyệt vời +1
Eric Uldall

1
Bạn chỉ có thể sudo -H -u otheruser bash -c 'crontab..." chạy crontab của một người dùng khác btw
Freedo

5

Theo mặc định với hầu hết các trình nền cron mặc định mà tôi đã thấy, đơn giản là không có cách nào để bảo cron chạy ngay tại đây ngay bây giờ. Nếu bạn đang sử dụng anacron, tôi nghĩ có thể chạy một cá thể riêng biệt ở nền trước.

Nếu tập lệnh của bạn không chạy đúng thì bạn không tính đến điều đó

  • tập lệnh đang chạy như một người dùng cụ thể
  • cron có một môi trường hạn chế (biểu hiện rõ ràng nhất của điều này là một con đường khác).

Từ crontab (5):

Một số biến môi trường được thiết lập tự động bởi trình nền cron (8). SHELL được đặt thành / bin / sh và LOGNAME và HOME được đặt từ dòng / etc / passwd của chủ sở hữu crontab. PATH được đặt thành "/ usr / bin: / bin". HOME, SHELL và PATH có thể bị ghi đè bởi các cài đặt trong crontab; LOGNAME là người dùng mà công việc đang chạy và không thể thay đổi.

Nói chung PATH là vấn đề lớn nhất, vì vậy bạn cần phải:

  • Hoàn toàn đặt PATH trong tập lệnh, trong khi thử nghiệm, thành / usr / bin: / bin. Bạn có thể làm điều này trong bash với export PATH = "/ usr / bin: / bin"
  • Hoàn toàn thiết lập PATH thích hợp mà bạn muốn ở đầu crontab. ví dụ: PATH = "/ usr / bin: / bin: / usr / local / bin: / usr / sbin: / sbin"

Nếu bạn cần chạy tập lệnh như một người dùng khác không có shell (ví dụ: dữ liệu www), hãy sử dụng sudo:

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

Tất nhiên, điều đầu tiên cần kiểm tra trước tất cả những điều đó, tất nhiên, là kịch bản của bạn thực sự làm những gì nó được cho là phải làm từ dòng lệnh. Nếu bạn không thể chạy nó từ dòng lệnh, rõ ràng nó sẽ không hoạt động với cron.


Cảm ơn bạn đã phản hồi kỹ lưỡng. Tôi nhận thức được hai vấn đề của việc chạy như một người dùng cụ thể và với một môi trường cụ thể. Như vậy, tôi đã đưa ra câu trả lời của riêng mình, mà bây giờ tôi sẽ đăng ...
Pistos

Nhân vật trốn thoát là một lý do hợp lệ cho công việc không chạy
Joe Phillips

2

Kịch bản của Marco không làm việc cho tôi vì một số lý do. Tôi không có thời gian để gỡ lỗi, vì vậy tôi đã viết một kịch bản Python cũng làm điều tương tự. Nó dài hơn, nhưng: thứ nhất, nó hiệu quả với tôi, và thứ hai, tôi thấy dễ hiểu hơn. Thay đổi "/ tmp / cron-env" thành nơi bạn đã lưu môi trường của mình. Đây là:

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

Chà, người dùng cũng giống như người bạn đặt trong mục crontab (hoặc người mà bạn đặt nó vào, thay vào đó), vì vậy đó là một việc không có trí tuệ. crontab(5) sẽ cung cấp cho bạn danh sách các biến môi trường được đặt, chỉ có một vài biến.


Nói cách khác, bạn đang nói không có cách nào để làm điều đó? Chỉ có cách giải quyết "đủ gần"?
Pistos

Không, tôi đang nói rằng bạn có thể làm điều đó, bằng cách sử dụng thông tin tôi đã cung cấp trong câu trả lời của mình.
womble

1

Trong hầu hết các crontab như vixie-cron, bạn có thể đặt các biến trong chính crontab như thế này và sau đó sử dụng / usr / bin / env để kiểm tra xem nó có hoạt động không. Bằng cách này, bạn có thể làm cho tập lệnh của mình hoạt động trong crontab sau khi bạn phát hiện ra có vấn đề gì với tập lệnh run-as-cron.

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

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

1

Giải pháp của Marco không hiệu quả với tôi nhưng kịch bản python của Noam đã hoạt động. Đây là một sửa đổi nhỏ cho kịch bản của Marco đã khiến nó hoạt động với tôi:

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

Các set -abiến xuất được thêm vào được xác định trong tập lệnh $ 1 và làm cho nó có sẵn để ra lệnh $ 2

Con trăn của ps Noam hoạt động vì môi trường 'xuất khẩu' sang tiến trình con.


1

Nếu đó là tập lệnh shell, điều này sẽ giúp bạn hiểu rõ hơn:

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>

Nó chắc chắn sẽ làm nổi bật một số vấn đề, nếu không phải là tất cả.


0

Tôi chưa bao giờ tìm thấy cách để chạy các công việc cron bằng tay nhưng cách viết này đề nghị thiết lập môi trường giống như cronjob sẽ có và chạy tập lệnh theo cách thủ công.


Không phải là những gì bạn đề xuất để làm những gì OP muốn biết làm thế nào?
womble

Đó sẽ là lý do tại sao tôi đưa vào liên kết đến bài viết mô tả cách thực hiện. Tôi không nghĩ cần phải sao chép-dán mọi thứ ở đây.
oneodd1

0

bạn có thể lập trình công việc để bắt đầu vào phút tiếp theo :)


7
59 giây là rất nhiều thời gian.
Stéphane Bruckert

OP đã đề cập đến khả năng này trong câu hỏi: "Có cách nào để làm điều này không? Phải đợi 60 giây để kiểm tra thay đổi tập lệnh là không thực tế."
Andrew Grimm

59 giây có lẽ ít hơn so với việc chọn và thực hiện bất kỳ giải pháp nào được đề xuất (và không được bảo đảm để làm việc). Khi tôi thấy những thiếu sót như vậy, tôi tự hỏi làm thế nào Linux trở thành một hệ điều hành máy chủ tiêu chuẩn thực tế như vậy. Không có bất kỳ sysadmin nghiêm trọng muốn kiểm tra công việc của họ?
Rolf

0

Tôi mân mê câu trả lời của Marco. Mã được hiển thị dưới đây, nhưng tôi sẽ duy trì kịch bản này ở đây .

Đưa ra crontab này:

# 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"

Phiên sử dụng mẫu:

$ 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

Đây là cronTest2, cần phải được gọi đúng để thiết lập các biến môi trường giống như 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" 

cronTestchạy cronTest2với các biến môi trường thích hợp được đặt:

#!/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"
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.