13

PHPでcrontabエントリを検証する最良の方法は何ですか? 正規表現または外部ライブラリを使用する必要がありますか? crontab ファイルにエントリを追加/削除する PHP スクリプトがありますが、時間間隔部分が有効な形式であることを確認する方法が必要です。

4

7 に答える 7

24

正規表現ではそれができないと誰が言ったのですか?

私の雇用主であるSalir.comの厚意により、このような検証を行う PHPUnit テストを以下に示します。自由に改変・配布してください。@author 通知と Web サイトへのリンクを残していただければ幸いです。

<?php
/**
 * @author Jordi Salvat i Alabart - with thanks to <a href="www.salir.com">Salir.com</a>.
 */

abstract class CrontabChecker extends PHPUnit_Framework_TestCase {
    protected function assertFileIsValidUserCrontab($file) {
        $f= @fopen($file, 'r', 1);
        $this->assertTrue($f !== false, 'Crontab file must exist');
        while (($line= fgets($f)) !== false) {
            $this->assertLineIsValid($line);
        }
    }

    protected function assertLineIsValid($line) {
        $regexp= $this->buildRegexp();
        $this->assertTrue(preg_match("/$regexp/", $line) !== 0);
    }

    private function buildRegexp() {
        $numbers= array(
            'min'=>'[0-5]?\d',
            'hour'=>'[01]?\d|2[0-3]',
            'day'=>'0?[1-9]|[12]\d|3[01]',
            'month'=>'[1-9]|1[012]',
            'dow'=>'[0-7]'
        );

        foreach($numbers as $field=>$number) {
            $range= "($number)(-($number)(\/\d+)?)?";
            $field_re[$field]= "\*(\/\d+)?|$range(,$range)*";
        }

        $field_re['month'].='|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec';
        $field_re['dow'].='|mon|tue|wed|thu|fri|sat|sun';

        $fields_re= '('.join(')\s+(', $field_re).')';

        $replacements= '@reboot|@yearly|@annually|@monthly|@weekly|@daily|@midnight|@hourly';

        return '^\s*('.
                '$'.
                '|#'.
                '|\w+\s*='.
                "|$fields_re\s+\S".
                "|($replacements)\s+\S".
            ')';
    }
}
于 2010-04-09T20:07:06.917 に答える
8

うーん、興味深い問題です。

実際に検証する場合は、正規表現だけでは不十分です。実際にエントリを解析し、各スケジューリング ビットを検証する必要があります。これは、各ビットが数値、月/曜日の文字列、範囲 (2-7)、セット (3、4、土曜日)、Vixie cron スタイルのショートカット (60/5)、または任意の組み合わせである可能性があるためです。上記の - 単一の正規表現アプローチは非常に毛むくじゃらで高速になります。

Vixie cronのプログラムを使用しcrontabて検証するだけでは十分ではありません。実際には完全には検証されないからです。crontabあらゆる種類の違法なものを受け入れることができます。

Dave Taylor の Wicked Cool Shell Scripts ( Google ブックスへのリンク) には、部分的な検証を行う sh スクリプトがあります。興味深い議論が見つかりました。コードを使用または適合させることもできます。

また、あなたの言うことを実行する2つのPHPクラスへのリンクも見つけました(品質は評価していません):

別のアプローチ (アプリで何をする必要があるかによって異なります) は、信頼できない文字列を検証しようとするのではなく、PHP にプログラムで crontab エントリを作成して挿入させることです。次に、「crontab エントリを作成する」UI を作成するだけで済みます。これは、非常に複雑なスケジュールの組み合わせが必要ない場合は簡単です。

于 2008-10-24T23:12:57.233 に答える
3

素晴らしいソリューションを投稿してくれた Jordi Salvat i Alabart に感謝します。

Jordi Salvat i Alabart によって投稿された既存のソリューションのみを変更しました。それはうまくいきましたが、グループをキャプチャして特定の部分を抽出したかったのです。crontab レコードの特定の部分を抽出できるように、非キャプチャ括弧を追加しました。http://www.regexplanet.com/advanced/java/index.htmlで出力正規表現をテストすると、使用するキャプチャ グループを簡単に確認できます 。

<?php
/**
 * @author Jordi Salvat i Alabart - with thanks to <a href="www.salir.com">Salir.com</a>.
 */

function buildRegexp() {
    $numbers = array(
        'min' => '[0-5]?\d',
        'hour' => '[01]?\d|2[0-3]',
        'day' => '0?[1-9]|[12]\d|3[01]',
        'month' => '[1-9]|1[012]',
        'dow' => '[0-6]'
    );

    foreach ($numbers as $field => $number) {
        $range = "(?:$number)(?:-(?:$number)(?:\/\d+)?)?";
        $field_re[$field] = "\*(?:\/\d+)?|$range(?:,$range)*";
    }

    $field_re['month'].='|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec';
    $field_re['dow'].='|mon|tue|wed|thu|fri|sat|sun';

    $fields_re = '(' . join(')\s+(', $field_re) . ')';

    $replacements = '@reboot|@yearly|@annually|@monthly|@weekly|@daily|@midnight|@hourly';

    return '^\s*(' .
            '$' .
            '|#' .
            '|\w+\s*=' .
            "|$fields_re\s+" .
            "|($replacements)\s+" .
            ')' .
            '([^\\s]+)\\s+' .
            '(.*)$';
}

このコードは正規表現を生成します:

^\s*($|#|\w+\s*=|(\*(?:\/\d+)?|(?:[0-5]?\d)(?:-(?:[0-5]?\d)(?:\/\d+)?)?(?:,(?:[0-5]?\d)(?:-(?:[0-5]?\d)(?:\/\d+)?)?)*)\s+(\*(?:\/\d+)?|(?:[01]?\d|2[0-3])(?:-(?:[01]?\d|2[0-3])(?:\/\d+)?)?(?:,(?:[01]?\d|2[0-3])(?:-(?:[01]?\d|2[0-3])(?:\/\d+)?)?)*)\s+(\*(?:\/\d+)?|(?:0?[1-9]|[12]\d|3[01])(?:-(?:0?[1-9]|[12]\d|3[01])(?:\/\d+)?)?(?:,(?:0?[1-9]|[12]\d|3[01])(?:-(?:0?[1-9]|[12]\d|3[01])(?:\/\d+)?)?)*)\s+(\*(?:\/\d+)?|(?:[1-9]|1[012])(?:-(?:[1-9]|1[012])(?:\/\d+)?)?(?:,(?:[1-9]|1[012])(?:-(?:[1-9]|1[012])(?:\/\d+)?)?)*|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\s+(\*(?:\/\d+)?|(?:[0-6])(?:-(?:[0-6])(?:\/\d+)?)?(?:,(?:[0-6])(?:-(?:[0-6])(?:\/\d+)?)?)*|mon|tue|wed|thu|fri|sat|sun)\s+|(@reboot|@yearly|@annually|@monthly|@weekly|@daily|@midnight|@hourly)\s+)([^\s]+)\s+(.*)$

または、この正規表現を生成する Java の代替 (@X のものなし):

public static String buildRegex(){
    // numbers intervals and regex
    Map<String, String> numbers = new HashMap<String, String>();
    numbers.put("min", "[0-5]?\\d");
    numbers.put("hour", "[01]?\\d|2[0-3]");
    numbers.put("day", "0?[1-9]|[12]\\d|3[01]");
    numbers.put("month", "[1-9]|1[012]");
    numbers.put("dow", "[0-6]");

    Map<String, String> field_re = new HashMap<String, String>();

    // expand regex to contain different time specifiers
    for(String field : numbers.keySet()){
        String number = numbers.get(field);
        String range = "(?:"+number+")(?:-(?:"+number+")(?:\\/\\d+)?)?";
        field_re.put(field, "\\*(?:\\/\\d+)?|"+range+"(?:,"+range+")*");
    }

    // add string specifiers
    String monthRE = field_re.get("month");
    monthRE = monthRE + "|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec";
    field_re.put("month", monthRE);

    String dowRE = field_re.get("dow");
    dowRE = dowRE + "|mon|tue|wed|thu|fri|sat|sun";
    field_re.put("dow", dowRE);

    StringBuilder fieldsReSB = new StringBuilder();
    fieldsReSB.append("^\\s*(")
            .append("$")
            .append("|#")
            .append("|\\w+\\s*=")
            .append("|");        
            .append("(")
            .append(field_re.get("min")).append(")\\s+(")
            .append(field_re.get("hour")).append(")\\s+(")
            .append(field_re.get("day")).append(")\\s+(")
            .append(field_re.get("month")).append(")\\s+(")
            .append(field_re.get("dow"))
            .append(")")
            .append("\\s+)")
            .append("([^\\s]+)\\s+")
            .append("(.*)$");

    return fieldsReSB.toString();
}
于 2012-05-06T16:01:18.910 に答える
2

Cron 式の検証に使用できる素敵な PHP ライブラリがあります。

このライブラリを composer 経由でインストールするには:

composer require mtdowling/cron-expression

Cron 式が有効かどうかを確認するには

$isValid = Cron\CronExpression::isValidExpression($expression);
于 2016-11-07T15:25:01.120 に答える
1

正規表現を使用すると、かなり簡単に実行できるはずです。実際、Google で既存の正規表現を見つけることができたとしても、私は驚かないでしょう。これはテストされていませんが、おそらく次のようなものです。

/^((\*)|(\d+((-\d+)|(,\d+)+))\s+){5}/
于 2008-10-24T22:45:58.050 に答える
0

Jordi Salvat i Alabart と ph4r05 に感謝します。

PHPに投稿された既存のソリューションを少し変更しました。正規表現を生成するための Perl の代替:

sub _BuildRegex {
    my $number = {
            'min'   =>      '[0-5]?\d',
            'hour'  =>      '[01]?\d|2[0-3]',
            'day'   =>      '0?[1-9]|[12]\d|3[01]',
            'month' =>      '[1-9]|1[012]',
            'dow'   =>      '[0-6]'
    };

    my $field_re = {};
    foreach my $nmb ( qw/min hour day month dow/ ) {
            my $range = "(?:$number->{$nmb})(?:-(?:$number->{$nmb})(?:\\/\\d+)?)?";
            $field_re->{$nmb} = "\\*(?:\\/\\d+)?|$range(?:,$range)*";
    }

    $field_re->{'month'} .='|[jJ]an|[fF]eb|[mM]ar|[aA]pr|[mM]ay|[jJ]un|[jJ]ul|[aA]ug|[sS]ep|[oO]ct|[nN]ov|[dD]ec';
    $field_re->{'dow'} .= '|[mM]on|[tT]ue|[wW]ed|[tT]hu|[fF]ri|[sS]at|[sS]un';

    my $ff = [];
    push @$ff, $field_re->{$_} foreach ( qw/min hour day month dow/ );

    my $fields_req = '(' . join(')\s+(', @$ff) . ')';

    my $replacements = '@reboot|@yearly|@annually|@monthly|@weekly|@daily|@midnight|@hourly';

    return '^\s*(' .
         '$' .
         '|#' .
         '|\w+\s*=' .
         "|$fields_req\\s+" .
         "|($replacements)\\s+" .
         ')' .
         '([^\\s]+)\\s+' .
         '(.*)$';
}
于 2014-12-11T18:06:35.693 に答える