PHPでのCrontabエントリの検証
-
04-07-2019 - |
質問
crontabエントリをPHPで検証する最良の方法は何ですか?正規表現または外部ライブラリを使用する必要がありますか? crontabファイルからエントリを追加/削除するPHPスクリプトを持っていますが、時間間隔の部分が有効な形式であることを確認する方法が必要です。
解決
うーん、興味深い問題。
実際に検証する場合、正規表現では十分ではありません。エントリを実際に解析し、各スケジューリングビットを検証する必要があります。これは、各ビットが数字、月/日文字列、範囲(2〜7)、セット(3、4、土曜日)、Vixie cronスタイルのショートカット(60/5)、または任意の組み合わせであるためです。上記のうち、単一の正規表現アプローチは非常に毛深い、高速になります。
Vixie cronの crontab
プログラムを使用して検証するだけでは、実際には完全には検証されないため、十分ではありません。 crontab
を取得して、あらゆる種類の違法なものを受け入れることができます。
デイブテイラーの邪悪なクールシェルスクリプト( Googleブックスのリンク)には、部分検証を行うshスクリプトがあります。この議論は興味深いものでした。また、コードを使用または適合させることもできます。
また、あなたが言うことをする2つのPHPクラスへのリンクを示しました(その品質は評価していません):
- http://www.phpclasses.org/browse/package/1189.html
- http://www.phpclasses.org/browse/package/1985.html
別のアプローチ(アプリの実行内容に応じて)は、PHPにプログラムでcrontabエントリを構築して挿入させることです。そのため、信頼できない文字列を検証しようとするのではなく、常に有効であることがわかります。次に、「crontabエントリを作成」するだけで済みます。 UI。これは、本当に複雑なスケジューリングの組み合わせを必要としない場合に簡単になります。
他のヒント
正規表現ではできないと誰が言ったのですか?
私の雇い主の 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".
')';
}
}
優れたソリューションを投稿してくれた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*(' .
'
このコードは正規表現を生成します:
^\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("<*>quot;)
.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("(.*)<*>quot;);
return fieldsReSB.toString();
}
.
'|#' .
'|\w+\s*=' .
"|$fields_re\s+" .
"|($replacements)\s+" .
')' .
'([^\\s]+)\\s+' .
'(.*)
このコードは正規表現を生成します:
<*>
またはこの正規表現を生成するためのJavaの代替(@Xなし):
<*>;
}
このコードは正規表現を生成します:
<*>またはこの正規表現を生成するためのJavaの代替(@Xなし):
<*>Cron式の検証に使用できる素晴らしいPHPライブラリがあります:
コンポーザー経由でこのライブラリをインストールするには:
composer require mtdowling/cron-expression
Cron式が有効かどうかを確認するには
$isValid = Cron\CronExpression::isValidExpression($expression);
正規表現を使用すると、かなり簡単にできるはずです。実際、Googleでそのための既存の正規表現を見つけることができても驚かないでしょう。これはテストされていませんが、おそらく次のようなものです:
/^((\*)|(\d+((-\d+)|(,\d+)+))\s+){5}/
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->{ Jordi Salvat i Alabartとph4r05に感謝します。
phpに投稿された既存のソリューションを少し修正しました。正規表現を生成するためのPerlの代替:
<*>} 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+' .
'(.*);
}
次のパターンを使用します。
/ ^((?:[1-9]?\ d | \ *)\ s *(?:(?:[\ /-] [1-9]?\ d)|(?:、[ 1-9]?\ d)+)?\ s *){5} $ /
PHPの場合:
<?php
$cron = "*/5 1-2 3 3,4,5 *";
$result = preg_match( "/^((?:[1-9]?\d|\*)\s*(?:(?:[\/-][1-9]?\d)|(?:,[1-9]?\d)+)?\s*){5}$/", $cron, $matches);
print_r($matches);