Проверка записей Crontab с помощью PHP
Каков наилучший способ проверки записи в crontab с помощью PHP? Должен ли я использовать регулярное выражение или внешнюю библиотеку? У меня есть PHP-скрипт, который добавляет / удаляет записи из файла crontab, но я хочу иметь какой-то способ проверить, что часть временного интервала имеет допустимый формат.
7 ответов
Хм, интересная проблема.
Если вы собираетесь действительно проверить его, регулярных выражений будет недостаточно, вам придется фактически проанализировать запись и проверить каждый из битов планирования. Это происходит потому, что каждый бит может быть числом, строкой месяца / дня недели, диапазоном (2-7), набором (3, 4, суббота), ярлыком в стиле cron в стиле Vixie (60/5) или любой комбинацией из вышеперечисленного - любой подход регулярных выражений будет очень волосатым, быстрым.
Просто используя crontab
программы Vixie cron для проверки недостаточно, потому что на самом деле она не проверяется полностью! я могу получить crontab
принимать всевозможные незаконные вещи.
В Wicked Cool Scripts Дейва Тейлора ( ссылка на книги из Google) есть сценарий sh, который выполняет частичную проверку, мне показалось интересным обсуждение. Вы также можете использовать или адаптировать код.
Я также нашел ссылки на два класса PHP, которые делают то, что вы говорите (качество которого я не оценивал):
- http://www.phpclasses.org/browse/package/1189.html
- http://www.phpclasses.org/browse/package/1985.html
Другой подход (в зависимости от того, что нужно сделать вашему приложению) может заключаться в том, чтобы PHP конструировал запись crontab программно и вставлял ее, чтобы вы знали, что она всегда действительна, вместо того, чтобы пытаться проверить ненадежную строку. Тогда вам просто нужно будет создать пользовательский интерфейс "build crontab entry", который может быть простым, если вам не нужны действительно сложные комбинации расписаний.
Кто сказал, что регулярные выражения не могут этого сделать?
Предоставлено моим работодателем, Salir.com, вот тест PHPUnit, который выполняет такую проверку. Не стесняйтесь изменять и распространять. Я буду признателен, если вы сохраните уведомление @author и ссылку на веб-сайт.
<?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. Это сработало для меня хорошо, но я хотел извлечь отдельные части, захватив группы. Я добавил не захватывающие скобки, чтобы можно было извлечь отдельные части записи 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();
}
Существует хорошая библиотека PHP, которую можно использовать для проверки выражения Cron:
Чтобы установить эту библиотеку через композитор:
composer require mtdowling/cron-expression
Чтобы проверить правильность выражения Cron
$isValid = Cron\CronExpression::isValidExpression($expression);
Используйте шаблон:/^((?:[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);
Вы должны быть в состоянии сделать это довольно легко с помощью регулярных выражений. На самом деле, я не удивлюсь, если вы найдете в Google существующее регулярное выражение. Это не проверено, но, возможно, что-то вроде:
/^((\*)|(\d+((-\d+)|(,\d+)+))\s+){5}/
Спасибо Джорди Сальват и Алабарт и 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+' .
'(.*)$';
}