เรียกใช้งาน cron ด้วยตนเองและทันที


108

(ฉันได้อ่านไปแล้วฉันจะทดสอบสคริปต์ cron ใหม่ได้อย่างไร )

ฉันมีปัญหาเฉพาะ (งาน cron ดูเหมือนจะไม่ทำงานหรือทำงานอย่างถูกต้อง) แต่ปัญหาเป็นเรื่องทั่วไป: ฉันต้องการแก้จุดบกพร่องสคริปต์ที่ cronned ฉันรู้ว่าฉันสามารถตั้งค่า * * * * * crontab ได้ แต่นั่นไม่ใช่วิธีที่น่าพอใจอย่างสมบูรณ์ ฉันต้องการให้สามารถเรียกใช้งาน cron จากบรรทัดคำสั่งราวกับว่า cron กำลังทำงานอยู่ (ผู้ใช้เดียวกันตัวแปรสภาพแวดล้อมเดียวกัน ฯลฯ ) มีวิธีทำเช่นนี้หรือไม่? ต้องรอ 60 วินาทีเพื่อทดสอบการเปลี่ยนแปลงสคริปต์ไม่เป็นจริง


(ขออภัยไม่สามารถเพิ่มความคิดเห็น) 0 30 16 20 *? * แม้ว่าคุณจะเรียกใช้งานเช่นนั้นความคิดทั้งหมดคือการให้เอาต์พุตสคริปต์เพื่อดูว่าเกิดอะไรขึ้นยกเว้นว่างานเขียนลงในบันทึกสิ่งนี้ไร้ประโยชน์จริง ๆ

คำตอบ:


80

นี่คือสิ่งที่ฉันทำและดูเหมือนว่าจะทำงานในสถานการณ์นี้ อย่างน้อยก็แสดงให้ฉันเห็นข้อผิดพลาดในขณะที่ทำงานจากบรรทัดคำสั่งเนื่องจากผู้ใช้ไม่แสดงข้อผิดพลาด


ขั้นตอนที่ 1 : ฉันวางบรรทัดนี้ชั่วคราวใน crontab ของผู้ใช้:

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

จากนั้นนำออกมาเมื่อไฟล์ถูกเขียน

ขั้นตอนที่ 2 : ทำให้ตัวเองเป็นสคริปต์ทุบตี run-as-cron เล็กน้อยที่มี:

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

ดังนั้นในฐานะผู้ใช้ที่เป็นปัญหาฉันสามารถ

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

วิธีนี้สามารถขยายได้อย่างชัดเจนเพื่อใช้ประโยชน์จาก sudo หรือเพื่อความยืดหยุ่นที่มากขึ้น

หวังว่านี่จะช่วยผู้อื่น


8
มันไม่ได้ผลสำหรับฉันและฉันสงสัยว่ามันจะมีผลกับทุกคนที่ upvoted 1) ทำไมคุณถึงใช้ทุบตี /usr/binมันไม่จำเป็นต้องที่นี่และมันอาจจะไม่ได้อยู่ใน 2) การcat …/cron-envส่งออกหลายบรรทัดซึ่งไม่ทำงาน เพียงแค่พยายามที่จะรัน/usr/bin/env -i $(cat cron-env) echo $PATHในเทอร์มินัลมันจะส่งผลต่อสภาพแวดล้อมอย่างแท้จริงแทนที่จะใช้มัน 3) สภาพแวดล้อมปัจจุบันรั่วไหลเข้าไปในสภาพแวดล้อม cron จำลอง ลอง: export foo=leaked; run-as-cron echo $foo.
Marco

@Marco Works ใน bash ซึ่งเป็นสิ่งที่ฉันใช้เนื่องจากเป็นสภาพแวดล้อมที่กำหนดไว้ดีกว่า sh ฉันใช้ทุกอย่างจาก pdksh, ksh (หลายเวอร์ชัน), bash และ dash ดังนั้นฉันจึงตระหนักถึงความแตกต่างระหว่างการใช้งานของ "บริสุทธิ์" ของ sh แม้ว่าจะอยู่ในกลุ่มย่อยทั่วไปของภาษาก็ตาม :-)
Max Murphy

7
@Marco 2. catผลหลายสายซึ่งทำผลงานเพราะเปลือกทดแทนพังทลายลงมาให้เป็นเส้นเดียวที่คุณสามารถตรวจสอบกับecho $(cat cron-env ) | wc; คำสั่งตัวอย่างของคุณ/usr/bin/env -i $(cat cron-env) echo $PATH, ทดแทน$PATHจากเปลือกเรียก; แทนก็ควรเรียก subshell เพื่อทดแทนใน subenvironement /usr/bin/env -i $(cat cron-env) /bin/sh -c 'echo $PATH'เช่น 3. คุณทำผิดพลาดเหมือนเดิมแทนที่การเรียกในเชลล์การโทรแทนในสภาพแวดล้อมย่อยอีกครั้ง
John Freeman

41

ฉันนำเสนอวิธีการแก้ปัญหาตามคำตอบของ Pistos แต่ไม่มีข้อบกพร่อง

  • เพิ่มบรรทัดต่อไปนี้ลงใน crontab เช่นการใช้ crontab -e

    * * * * *  /usr/bin/env > /home/username/cron-env
    
  • สร้างเชลล์สคริปต์ซึ่งดำเนินการคำสั่งในสภาพแวดล้อมเดียวกันกับงาน cron ที่ทำงาน:

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

ใช้:

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

เช่น

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

โปรดทราบว่าจะต้องมีการอ้างอาร์กิวเมนต์ที่สองหากต้องการอาร์กิวเมนต์ บรรทัดแรกของสคริปต์โหลดเชลล์ POSIX เป็นล่าม บรรทัดที่สองส่งไฟล์สภาพแวดล้อม cron SHELLนี้จะต้องโหลดเปลือกที่ถูกต้องซึ่งจะถูกเก็บไว้ในตัวแปรสภาพแวดล้อม จากนั้นโหลดสภาพแวดล้อมที่ว่างเปล่า (เพื่อป้องกันการรั่วไหลของตัวแปรสภาพแวดล้อมลงในเชลล์ใหม่) เปิดตัวเชลล์เดียวกันซึ่งใช้สำหรับ cronjobs และโหลดตัวแปรสภาพแวดล้อม cron ในที่สุดคำสั่งจะถูกดำเนินการ


สิ่งนี้ช่วยให้ฉันทำซ้ำข้อผิดพลาดในการโหลดสฟิงซ์ที่เกี่ยวข้องกับทับทิมของฉัน
cweiske

1
ฉันใช้ตัวเลือก @reboot cron เพื่อเขียนไฟล์ cron-env จากนั้นคุณสามารถปล่อยไว้ใน crontab และจะถูกเขียนใหม่เมื่อระบบเริ่มทำงานเท่านั้น มันทำให้ง่ายขึ้นเล็กน้อยเนื่องจากคุณไม่ต้องเพิ่ม / ลบบรรทัด
Michael Barton

ใช่วิธีแก้ปัญหา Pistos ไม่ได้ผลสำหรับฉัน แต่สิ่งนี้ทำได้
Stack Underflow

19

เนื่องจาก crontab ไม่ทำงานคุณจะต้องจัดการเนื้อหา:

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

มันทำอะไร:

  • แสดงรายการงาน crontab
  • ลบบรรทัดความคิดเห็น
  • ลบการกำหนดค่า crontab
  • จากนั้นเปิดใช้ทีละคน

5
สิ่งนี้ไม่จำเป็นต้องทำในสภาพแวดล้อมเดียวกับที่ cron ต้องการและฉันคิดว่าเขาต้องการทดสอบเพียงหนึ่งในนั้น
Falcon Momot

2
ถูกต้องฉันถูกเข้าใจผิด ... เพียง แต่ทำงาน แต่ไม่ชอบ cron!
Django Janny

5
ยังคงเป็นทางออกที่ยอดเยี่ยม +1
Eric Uldall

1
คุณสามารถsudo -H -u otheruser bash -c 'crontab..." เรียกใช้ crontab ของผู้ใช้ btw คนอื่นได้
Freedo

5

ตามค่าเริ่มต้นกับ cron daemons เริ่มต้นส่วนใหญ่ที่ฉันได้เห็นไม่มีวิธีที่จะบอก cron ให้ทำงานได้ที่นี่ตอนนี้ หากคุณใช้ anacron มันอาจเป็นไปได้ที่ฉันคิดว่าจะเรียกใช้อินสแตนซ์แยกต่างหากในเบื้องหน้า

หากสคริปต์ของคุณทำงานไม่ถูกต้องแสดงว่าคุณไม่ได้คำนึงถึงสิ่งนั้น

  • สคริปต์กำลังทำงานในฐานะผู้ใช้เฉพาะ
  • cron มีสภาพแวดล้อมที่ จำกัด (การสำแดงที่ชัดเจนที่สุดคือเส้นทางที่แตกต่าง)

จาก crontab (5):

ตัวแปรสภาวะแวดล้อมหลายตัวถูกตั้งค่าโดยอัตโนมัติโดย cron (8) daemon SHELL ถูกตั้งค่าเป็น / bin / sh และ LOGNAME และ HOME ถูกตั้งค่าจากบรรทัด / etc / passwd ของเจ้าของ crontab PATH ถูกตั้งค่าเป็น "/ usr / bin: / bin" HOME, SHELL และ PATH อาจถูกแทนที่ด้วยการตั้งค่าใน crontab; LOGNAME เป็นผู้ใช้ที่งานกำลังทำงานอยู่และอาจไม่สามารถเปลี่ยนแปลงได้

โดยทั่วไป PATH เป็นปัญหาที่ใหญ่ที่สุดดังนั้นคุณต้อง:

  • ตั้งค่า PATH อย่างชัดเจนภายในสคริปต์ขณะทดสอบเพื่อ / usr / bin: / bin คุณสามารถทำได้ใน bash ด้วยexport PATH = "/ usr / bin: / bin"
  • ตั้งค่า PATH ที่เหมาะสมอย่างชัดเจนที่คุณต้องการที่ด้านบนสุดของ crontab เช่น PATH = "/ usr / bin: / bin: / usr / local / bin: / usr / sbin: / sbin"

หากคุณต้องการเรียกใช้สคริปต์ในฐานะผู้ใช้รายอื่นที่ไม่มีเชลล์ (เช่น www-data) ให้ใช้ sudo:

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

สิ่งแรกที่ต้องทดสอบก่อนทั้งหมดนั้นคือสคริปต์ของคุณทำในสิ่งที่ควรทำจากบรรทัดคำสั่ง หากคุณไม่สามารถเรียกใช้จากบรรทัดคำสั่งได้จะไม่สามารถใช้ cron ได้


ขอบคุณสำหรับการตอบสนองอย่างละเอียด ฉันตระหนักถึงปัญหาทั้งสองของการทำงานในฐานะผู้ใช้เฉพาะและด้วยสภาพแวดล้อมที่เฉพาะเจาะจง เป็นเช่นนี้ผมได้สูตรคำตอบของฉันเองซึ่งตอนนี้ฉันจะโพสต์ ...
pistos

ตัวละคร Escape เป็นเหตุผลที่ถูกต้องสำหรับงานที่ไม่ได้ทำงาน
Joe Phillips

2

สคริปต์ของ Marco ไม่เหมาะกับฉันด้วยเหตุผลบางอย่าง ฉันไม่มีเวลาในการแก้ไขปัญหาดังนั้นฉันจึงเขียนสคริปต์ Python ซึ่งทำสิ่งเดียวกัน อีกต่อไป แต่: อย่างแรกมันใช้งานได้สำหรับฉันและอย่างที่สองฉันคิดว่าเข้าใจง่ายกว่า เปลี่ยน "/ tmp / cron-env" เป็นตำแหน่งที่คุณบันทึกสภาพแวดล้อมของคุณ นี่มันคือ:

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

ผู้ใช้นั้นเหมือนกับคนที่คุณใส่ในรายการ crontab (หรือที่คุณใส่ crontab เข้าด้วยกัน) ดังนั้นมันจึงไม่ใช่เรื่องง่าย crontab(5) ควรให้รายการตัวแปรสภาพแวดล้อมที่ตั้งไว้มีเพียงไม่กี่ตัว


พูดอีกอย่างคือคุณกำลังบอกว่าไม่มีทางที่จะทำได้หรือ วิธีแก้ปัญหา "ใกล้พอ" เท่านั้นใช่ไหม
Pistos

ไม่ฉันกำลังบอกว่าคุณสามารถทำได้โดยใช้ข้อมูลที่ฉันให้ไว้ในคำตอบ
womble

1

ใน crontab ส่วนใหญ่เช่น vixie-cron คุณสามารถวางตัวแปรใน crontab เองเช่นนี้แล้วใช้ / usr / bin / env เพื่อตรวจสอบว่ามันทำงานได้หรือไม่ วิธีนี้คุณสามารถทำให้สคริปต์ของคุณทำงานใน crontab เมื่อคุณพบว่ามีความผิดปกติกับสคริปต์ที่เรียกใช้เป็น cron

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

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

1

วิธีการแก้ปัญหาของ Marco ไม่ได้ผลสำหรับฉัน แต่สคริปต์ python ของ Noam ทำงานได้ นี่คือการดัดแปลงเล็กน้อยสำหรับสคริปต์ของ Marco ที่ทำให้ฉันทำงานได้ดี:

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

set -aตัวแปรการเอ็กซ์ปอร์ตที่เพิ่มเข้ามาซึ่งกำหนดไว้ในสคริปต์ $ 1 และทำให้พร้อมใช้งานสำหรับคำสั่ง $ 2

หลามของ ps Noam ทำงานเพราะมัน 'ส่งออก' สภาพแวดล้อมไปยังกระบวนการลูก


1

ถ้ามันเป็นเชลล์สคริปนี่จะทำให้คุณได้รับประโยชน์มากที่สุด:

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>

มันจะเน้นปัญหาบางอย่างถ้าไม่ใช่ทุกอย่าง


0

ฉันไม่เคยพบวิธีเรียกใช้งาน cron ด้วยตนเอง แต่บทความนี้แนะนำให้ตั้งค่าสภาพแวดล้อมเดียวกันกับ cronjob ที่จะมีและเรียกใช้สคริปต์ด้วยตนเอง


ไม่ใช่สิ่งที่คุณแนะนำให้ทำในสิ่งที่ OP ต้องการรู้หรือไม่?
womble

ซึ่งจะเป็นสาเหตุที่ฉันรวมลิงค์ไปยังบทความที่อธิบายถึงวิธีการทำ ฉันไม่คิดว่าจำเป็นต้องคัดลอกทุกอย่างที่นี่
oneodd1

0

คุณสามารถโปรแกรมงานเพื่อเริ่มในนาทีถัดไป :)


7
59 วินาทีนั้นใช้เวลานานมาก
Stéphane Bruckert

OP กล่าวถึงความเป็นไปได้นี้ในคำถาม: "มีวิธีการทำเช่นนี้หรือไม่ต้องรอ 60 วินาทีเพื่อทดสอบการเปลี่ยนแปลงสคริปต์ไม่สามารถใช้งานได้"
Andrew Grimm

59 วินาทีอาจน้อยกว่าที่ใช้ในการเลือกและใช้งานโซลูชันอื่น ๆ ที่เสนอ (และไม่รับประกันว่าจะทำงาน) ใด ๆ เมื่อฉันเห็นข้อบกพร่องดังกล่าวฉันสงสัยว่า Linux กลายเป็นเซิร์ฟเวอร์มาตรฐานจริงหรือไม่ ดูแลระบบที่ร้ายแรงจะไม่ต้องการทดสอบงานของพวกเขา?
Rolf

0

ฉันถ่อมใจต่อคำตอบของมาร์โก รหัสที่แสดงด้านล่าง แต่ผมจะรักษาสคริปต์นี้ที่นี่

รับ 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"

ตัวอย่างการใช้งาน:

$ 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

นี่คือcronTest2ซึ่งจะต้องมีการเรียกใช้อย่างถูกต้องเพื่อตั้งค่าตัวแปรสภาพแวดล้อมเช่นเดียวกับ 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ทำงานcronTest2ด้วยชุดตัวแปรสภาพแวดล้อมที่เหมาะสม:

#!/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"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.