cronジョブを手動ですぐに実行する


108

新しいcronスクリプトをテストするにはどうすればよいですか?

特定の問題があります(cronジョブが実行されていないか、適切に実行されていないように見えます)が、問題は一般的なものです。cronスクリプトをデバッグしたいのですが。私は* * * * * crontab行をセットアップできることを知っていますが、それは完全に満足のいく解決策ではありません。cronが実行しているように(同じユーザー、同じ環境変数など)コマンドラインからcronジョブを実行できるようにしたいと思います。これを行う方法はありますか?スクリプトの変更をテストするために60秒待機することは実用的ではありません。


(申し訳ありませんがコメントを追加できません)0 30 16 20 *?*あなたがそのようなジョブを実行しても、全体的なアイデアは間違って何が起こっているのか確認するためにスクリプトの出力を提供することであるジョブは、ログへの書き込みをしない限り、これはquuiet無用である

回答:


80

これが私がやったことで、この状況でうまくいくようです。少なくとも、エラーが表示されますが、ユーザーとしてコマンドラインから実行してもエラーは表示されません。


ステップ1:ユーザーのcrontabにこの行を一時的に配置します。

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

ファイルが書き込まれたらそれを取り出しました。

ステップ2:自分自身を次のものを含む小さなrun-as-cron bashスクリプトにしました。

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

そのため、問題のユーザーとして、私はできました

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

このソリューションは、sudoなどを使用して柔軟性を高めるために明らかに拡張できます。

これが他の人の役に立つことを願っています。


8
これは私には役に立たず、賛成した人には役にたつかと思います。1)なぜbashを使用しているのですか?ここでは必要ありません/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はbashで動作します。shよりも定義された環境なので、これを使用します。私はpdksh、ksh(複数バージョン)、bash、dashのすべてを使用しているため、言語の共通サブセットに厳密に留まる場合でも、shの「純粋な」実装の違いを非常によく認識しています。:-)
マックスマーフィー

7
@Marco 2.はcat複数の行を出力しますが、これは機能します。シェルの置換はそれらを1行に折りたたむためecho $(cat cron-env ) | wcです。例のコマンド、は呼び出しシェルからの/usr/bin/env -i $(cat cron-env) echo $PATH代替$PATHです。代わりに、サブシェルを呼び出して、サブ環境で置換する必要があり/usr/bin/env -i $(cat cron-env) /bin/sh -c 'echo $PATH'ます。3.あなたは同じ間違いを犯しました。ここでも、サブ環境ではなく呼び出しシェルで置き換えます
ジョンフリーマン

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'

引数が必要な場合は、2番目の引数を引用符で囲む必要があることに注意してください。スクリプトの最初の行は、インタープリターとしてPOSIXシェルをロードします。2行目は、cron環境ファイルのソースです。これは、環境変数に保存されている正しいシェルをロードするために必要ですSHELL。次に、(新しいシェルへの環境変数の漏洩を防ぐために)空の環境をロードし、cronジョブに使用されるのと同じシェルを起動し、cron環境変数をロードします。最後に、コマンドが実行されます。


これは、ルビー関連のスフィンクスの読み込みエラーを再現するのに役立ちました。
cweiske

1
@reboot cronオプションを使用して、cron-envファイルを記述しました。その後、crontabに残すことができ、システムの起動時にのみ書き換えられます。行を追加/削除する必要がないため、少し簡単になります。
マイケルバートン

はい、Pistosソリューションは私のために動作しませんでしたが、これはやった
スタックアンダーフロー

19

crontabは仕事をしないので、その内容を操作します:

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

それが何をする:

  • crontabジョブをリストします
  • コメント行を削除する
  • crontab構成を削除します
  • その後、それらを1つずつ起動します

5
ただし、これは必ずしもcronと同じ環境で行われるとは限らず、そのうちの1つだけをテストしたいと思っていました。
ファルコンモモット

2
正しい、私は間違っていました...それはジョブを実行するだけですが、cronがするようなものではありません!
ジャンゴジャニー

5
まだ素晴らしいソリューション+1
エリックUldall

1
sudo -H -u otheruser bash -c 'crontab..." 別のユーザーのcrontabを実行するだけで済みます
Freedo

5

私が見たほとんどのデフォルトcronデーモンのデフォルトでは、今ここで実行するようにcronに指示する方法はありません。アナクロンを使用している場合は、フォアグラウンドで別のインスタンスを実行することが考えられます。

スクリプトが適切に実行されていない場合は、それを考慮していません

  • スクリプトは特定のユーザーとして実行されています
  • cronには制限された環境があります(これの最も明白な兆候は異なるパスです)。

crontab(5)から:

cron(8)デーモンによっていくつかの環境変数が自動的に設定されます。SHELLは/ bin / shに設定され、LOGNAMEとHOMEはcrontabの所有者の/ etc / passwd行から設定されます。PATHは「/ usr / bin:/ bin」に設定されます。HOME、SHELL、およびPATHは、crontabの設定によって上書きされる場合があります。LOGNAMEは、ジョブの実行元のユーザーであり、変更できません。

一般に、PATHが最大の問題であるため、次のことが必要です。

  • テスト中に、スクリプト内でPATHを/ usr / bin:/ binに明示的に設定します。export PATH = "/ usr / bin:/ bin"を使用してbashでこれを行うことができます
  • crontabの上部で適切なPATHを明示的に設定します。例:PATH = "/ usr / bin:/ bin:/ usr / local / bin:/ usr / sbin:/ sbin"

シェルなしで別のユーザーとしてスクリプトを実行する必要がある場合(www-dataなど)、sudoを使用します。

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

もちろん、そのすべての前に最初にテストすることは、スクリプトが実際にコマンドラインから実行するはずのことを実行することです。コマンドラインから実行できない場合は、cronからは明らかに動作しません。


徹底的な対応ありがとうございます。特定のユーザーとして、特定の環境で実行するという2つの問題を認識しています。このように、私は自分の答えを定式化したので、今投稿します
...-ピストス

エスケープ文字は、ジョブが実行されない正当な理由です
ジョーフィリップス

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)環境変数セットのリストを提供する必要がありますが、いくつかあります。


言い換えれば、あなたはそれをする方法がないと言っているのですか?「十分に近い」回避策のみですか?
ピスト

いいえ、回答で提供した情報を使用して実行できると言っています。
ウォンブル

1

たとえばvixie-cronなどのほとんどのcrontabでは、このようにcrontab自体に変数を配置し、/ usr / bin / envを使用して動作するかどうかを確認できます。このようにして、run-as-cronスクリプトの問題点を発見したら、crontabでスクリプトを機能させることができます。

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

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

1

マルコのソリューションはうまくいきませんでしたが、Noamのpythonスクリプトは機能しました。以下は、Marcoのスクリプトを少し修正したものです。

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

set -aスクリプト$ 1で定義され、コマンド$ 2で使用できるように追加されたエクスポート変数

ps Noamのpythonは、子プロセスに環境を「エクスポート」したため機能しました。


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が方法を知りたいと思っていることを行うように提案するのではありませんか
ウォンブル

これが、その方法を説明する記事へのリンクを含めた理由です。ここにすべてをコピー&ペーストする必要はないと思いました。
oneodd1 09年

0

次の分を開始するようにジョブをプログラムできます:)


7
59秒は長い時間です。
ステファンブルックケルト

OPは質問でこの可能性に言及しました:「これを行う方法はありますか?スクリプトの変更をテストするために60秒待たなければならないことは実用的ではありません。」
アンドリューグリム

おそらく59秒は、他の提案された(そして動作が保証されていない)ソリューションを選択して実装するのにかかるよりも短いでしょう。このような欠点を見ると、Linuxがどのように事実上の標準サーバーOSになったのかと思います。深刻なシステム管理者は自分の仕事をテストしたくないでしょうか?
ロルフ

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" 

cronTestcronTest2適切な環境変数を設定して実行します:

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