Каков наилучший способ проверки записи crontab с помощью PHP? Должен ли я использовать регулярное выражение или внешнюю библиотеку? У меня есть PHP-скрипт, который добавляет / удаляет записи из файла crontab, но хочет иметь некоторый способ проверить, что часть интервала времени находится в допустимом формате.
Хммм, интересная проблема.
Если вы собираетесь его действительно проверить, регулярного выражения не будет достаточно, вам придется фактически разобрать запись и проверить каждый бит планирования. Это связано с тем, что каждый бит может быть числом, месяцем / днем недели, диапазоном (2-7), набором (3, 4, суббота), ярлыком стиля Vixie cron (60/5) или любой комбинацией из вышеперечисленного – любой подход с одним регулярным выражением будет очень волосатым, быстрым.
Просто использовать программу crontab
Vixie cron для проверки недостаточно, потому что она фактически не проверяет полностью! Я могу заставить crontab
принять всевозможные незаконные вещи.
Скриншоты Dick Taylor's Wicked Cool Shell ( ссылка на книги Google ) имеют сценарий sh, который выполняет частичную проверку, я нашел интересную дискуссию. Вы также можете использовать или адаптировать код.
Я также добавил ссылки на два класса PHP, которые делают то, что вы говорите (качество которых я не оценил):
Другой подход (в зависимости от того, что нужно делать вашему приложению) может заключаться в том, чтобы PHP конструктивно вставлял запись crontab программно и вставлял ее, поэтому вы знаете, что она всегда действительна, а не пытается проверить ненадежную строку. Тогда вам просто нужно создать пользовательский интерфейс «build a 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, который опубликовал большое решение.
Я только изменил существующее решение, отправленное 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(); }
Вы должны сделать это довольно легко с регулярным выражением. На самом деле, я бы не удивился, если бы вы могли найти существующее регулярное выражение только для 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->{$_} 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);
Существует хорошая библиотека PHP, которая может использоваться для проверки выражения Cron:
Чтобы установить эту библиотеку через композитор:
composer require mtdowling/cron-expression
Чтобы проверить правильность выражения Cron
$isValid = Cron\CronExpression::isValidExpression($expression);