как проанализировать список сеансов экрана GNU?

#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/1

3. спасибо, это 2-е регулярное выражение выглядит безопаснее (при сравнении с другими форматами LC_TIME), чем ваше первое регулярное выражение ^^ думаете, вы могли бы добавить это к ответу?

4. @hanshenrik Я добавил и это тоже.

5. @hanshenrikhan если вы планируете раздувать шаблон с помощью именованных групп захвата, то полагайтесь на них при создании возвращаемого массива. 3v4l.org/dKRSK