#php #regex #parsing #gnu #gnu-screen
Вопрос:
как предполагается программно анализировать списки сеансов экрана GNU? я использую PHP здесь, но ответ на любом языке программирования был бы полезен. я пробовал делать это сам с регулярным выражением и, кажется, я довольно близок, но он неправильно анализирует сеансы со скобками в названии (что является крайним случаем, поддерживаемым экраном GNU).
этот
<?php
declare(strict_types=1);
function screen_list_sessions(): array
{
if (1) {
// test code
$raw = '
There are screens on:
2064.screen with parenthesis in name ( this is stupid ) (10/02/2021 12:07:22 PM) (Detached)
1996.test session with spaces (10/02/2021 12:00:35 PM) (Detached)
1985.testSessionWithDuplicateName (10/02/2021 12:00:23 PM) (Detached)
1974.testSessionWithDuplicateName (10/02/2021 12:00:16 PM) (Detached)
1963.testSessionWithDuplicateName (10/02/2021 12:00:09 PM) (Detached)
1871.testscreen (10/02/2021 11:59:23 AM) (Detached)
6 Sockets in /run/screen/S-root.
';
} else {
$raw = shell_exec('screen -list');
}
$matches = [];
$match_count = preg_match_all('/^s (?<session_id>d )\.(?<session_name>.*?)((?<session_creation_date>[sS] ?))s ((?<session_state>[sS] ?))s*?$/mu', $raw, $matches);
$ret = [];
for ($i = 0; $i < $match_count; $i) {
$ret[] = [
"session_id" => $matches["session_id"][$i],
"session_name" => $matches['session_name'][$i],
"session_creation_date" => $matches["session_creation_date"][$i],
"session_state" => $matches["session_state"][$i]
];
}
return $ret;
}
var_export(screen_list_sessions());
ВОЗВРАТ
$ php test.php
array (
0 =>
array (
'session_id' => '2064',
'session_name' => 'screen with parenthesis in name ',
'session_creation_date' => ' this is stupid ',
'session_state' => '10/02/2021 12:07:22 PM) (Detached',
),
1 =>
array (
'session_id' => '1996',
'session_name' => 'test session with spaces ',
'session_creation_date' => '10/02/2021 12:00:35 PM',
'session_state' => 'Detached',
),
2 =>
array (
'session_id' => '1985',
'session_name' => 'testSessionWithDuplicateName ',
'session_creation_date' => '10/02/2021 12:00:23 PM',
'session_state' => 'Detached',
),
3 =>
array (
'session_id' => '1974',
'session_name' => 'testSessionWithDuplicateName ',
'session_creation_date' => '10/02/2021 12:00:16 PM',
'session_state' => 'Detached',
),
4 =>
array (
'session_id' => '1963',
'session_name' => 'testSessionWithDuplicateName ',
'session_creation_date' => '10/02/2021 12:00:09 PM',
'session_state' => 'Detached',
),
5 =>
array (
'session_id' => '1871',
'session_name' => 'testscreen ',
'session_creation_date' => '10/02/2021 11:59:23 AM',
'session_state' => 'Detached',
),
)
edit: tried without regex, seems i got even closer, this
<?php
declare(strict_types=1);
function screen_list_sessions(): array
{
if (1) {
// test code
$raw = '
There are screens on:
2064.screen with parenthesis in name ( this is stupid ) (10/02/2021 12:07:22 PM) (Detached)
1996.test session with spaces (10/02/2021 12:00:35 PM) (Detached)
1985.testSessionWithDuplicateName (10/02/2021 12:00:23 PM) (Detached)
1974.testSessionWithDuplicateName (10/02/2021 12:00:16 PM) (Detached)
1963.testSessionWithDuplicateName (10/02/2021 12:00:09 PM) (Detached)
1871.testscreen (10/02/2021 11:59:23 AM) (Detached)
6 Sockets in /run/screen/S-root.
';
} else {
$raw = shell_exec('screen -list');
}
$raw = implode("n", array_filter(explode("n", $raw), function (string $line): bool {
// remove uninteresting lines; only lines that start with spaces are interesting..
$trimmed = ltrim($line);
if ($trimmed === $line) {
return false;
}
if (strlen($trimmed) < 1) {
// empty lines are also uninteresting
return false;
}
// interesting line
return true;
}));
$ret = [];
$lines = explode("n", $raw);
foreach ($lines as $key => $line) {
$current = [];
$lastParStart = strrpos($line, '(');
if ($lastParStart === false) {
throw new LogicException("line without "(": {$line}");
}
$lastParEnd = strrpos($line, ')');
if ($lastParEnd === false) {
throw new LogicException("line without ")": {$line}");
}
$current['session_state'] = substr($line, $lastParStart strlen('('), $lastParEnd - $lastParStart - strlen(')'));
$line = substr($line, 0, $lastParStart);
$lastParStart = strrpos($line, '(');
if ($lastParStart === false) {
throw new LogicException("line without 2nd "(": {$line}");
}
$lastParEnd = strrpos($line, ')');
if ($lastParEnd === false) {
throw new LogicException("line without 2nd ")": {$line}");
}
$current['session_creation_date'] = substr($line, $lastParStart strlen('('), $lastParEnd - $lastParStart - strlen(')'));
$line = substr($line, 0, $lastParStart - strlen("t"));
$dotPos = strpos($line, '.');
if (false === $dotPos) {
throw new LogicException("");
}
$current['session_id'] = trim(substr($line, 0, $dotPos));
// using trim() here probably means we won't 100% support sessions whose names starts or ends with space...
$current['session_name'] = trim(substr($line, $dotPos strlen(".")));
// just put keys in pretty-ish order
$current = [
"session_id" => $current['session_id'],
"session_name" => $current['session_name'],
"session_state" => $current['session_state'],
"session_creation_date" => $current['session_creation_date'],
];
$ret[] = $current;
}
return $ret;
}
var_export(screen_list_sessions());
С принтами
array (
0 =>
array (
'session_id' => '2064',
'session_name' => 'screen with parenthesis in name ( this is stupid )',
'session_state' => 'Detached',
'session_creation_date' => '10/02/2021 12:07:22 PM',
),
1 =>
array (
'session_id' => '1996',
'session_name' => 'test session with spaces',
'session_state' => 'Detached',
'session_creation_date' => '10/02/2021 12:00:35 PM',
),
2 =>
array (
'session_id' => '1985',
'session_name' => 'testSessionWithDuplicateName',
'session_state' => 'Detached',
'session_creation_date' => '10/02/2021 12:00:23 PM',
),
3 =>
array (
'session_id' => '1974',
'session_name' => 'testSessionWithDuplicateName',
'session_state' => 'Detached',
'session_creation_date' => '10/02/2021 12:00:16 PM',
),
4 =>
array (
'session_id' => '1963',
'session_name' => 'testSessionWithDuplicateName',
'session_state' => 'Detached',
'session_creation_date' => '10/02/2021 12:00:09 PM',
),
5 =>
array (
'session_id' => '1871',
'session_name' => 'testscreen',
'session_state' => 'Detached',
'session_creation_date' => '10/02/2021 11:59:23 AM',
),
)
(хотя, я думаю, этот подход не поддерживает разбор сеансов экрана, имена которых начинаются или заканчиваются пробелами)
Комментарии:
1. Попробуй
^s (?<session_id>d ).(?<session_name>.*?)((?<session_creation_date>d{2}/d{2}/d{4} d{2}:d{2}:d{2} [AP]M))s ((?<session_state>[sS] ?))s*?$
Посмотреть regex101.com/r/VU23am/1
Ответ №1:
Вы можете изменить шаблон, чтобы сделать session_creation_date
его более конкретным. Вы также можете пропустить сопоставление пробелов после по имени сеанса, сопоставив необязательные символы пробелов s*
после (вне) группы захвата.
^s (?<session_id>d ).(?<session_name>.*?)s*((?<session_creation_date>d{2}/d{2}/d{4} d{2}:d{2}:d{2} [AP]M))s ((?<session_state>[sS] ?))s*?$
Демонстрация регулярных выражений
Менее строгим шаблоном может быть использование 2 классов отрицаемых символов [^()]
в конце шаблона для сопоставления текста между скобками
^s (?<session_id>d ).(?<session_name>.*?)s*((?<session_creation_date>[^()] ))s ((?<session_state>[^()] ))s*$
Демонстрация регулярных выражений
Обратите внимание, что в вашем шаблоне [sS]
соответствует любому символу, включая новые строки, что может привести к слишком большому совпадению.
s*
Они также могут совпадать с новыми строками, и s*?
в конце шаблона не обязательно должно быть не жадным, так как оно уже совпадает до конца строки.
Комментарии:
1. это кажется идеальным, спасибо! но я бы не стал проходить мимо экрана GNU, чтобы взять формат даты из переменной среды LC_TIME, интересно, можно ли сделать его более устойчивым к различным форматам даты
2. @hanshenrik Что вы также можете сделать, так это сопоставить все между скобками, используя вместо этого 2 отрицательных класса символов.
^s (?<session_id>d ).(?<session_name>.*?)s*((?<session_creation_date>[^()] ))s ((?<session_state>[^()] ))s*$
Видишь regex101.com/r/tPvAPa/13. спасибо, это 2-е регулярное выражение выглядит безопаснее (при сравнении с другими форматами LC_TIME), чем ваше первое регулярное выражение ^^ думаете, вы могли бы добавить это к ответу?
4. @hanshenrik Я добавил и это тоже.
5. @hanshenrikhan если вы планируете раздувать шаблон с помощью именованных групп захвата, то полагайтесь на них при создании возвращаемого массива. 3v4l.org/dKRSK