Среда, 06.08.2025 10:25

Разбор (парсинг) вывода программы с помощью регулярных выражений. Запуск внешних программ в Qt6. Часть 5

Разбор (парсинг) вывода программы с помощью регулярных выражений. Запуск внешних программ в Qt6. Часть 5

В прошлых статьях мы рассмотрели запуск внешней программы в отдельной нити и обработку ошибок во время запуска и её выполнения.

Сегодня мы рассмотрим разбор (парсинг) вывода программы с помощью регулярных выражений. Для примера мы возьмем вывод команды dir и подсчитаем размер файлов в папке.

Создаем новый класс на базе класса Worker

До сих пор мы использовали класс Worker для вывода результата работы программы. Но при работе с разными программами нам могут потребоваться разные обработчики. Мы не будем пока что реализовывать интерфейс для работы, а начнем с переопределения методов в наследованных классах.

Изменим метод onRead класса Worker сделав его виртуальным, для этого в заголовке заменим

void onRead(const QString &data);

на 

virtual void onRead(const QString &data);

Этого достаточно, чтобы в наследуемом классе появилась возможность заменить этот метод своим.

Допустим, нам нужно узнать общий размер файлов в текущей папке. Для этого мы создадим отдельный класс DirSizeWorker и наследуем его от Worker.

Создадим класс DirSizeWorker:

Заголовок:

#ifndef DIRSIZEWORKER_H
#define DIRSIZEWORKER_H

#include "worker.h"

class DirSizeWorker : public Worker
{
public:
    explicit DirSizeWorker(QObject *parent = nullptr);
    virtual void onRead(const QString &data);
};

#endif // DIRSIZEWORKER_H

Реализация:

#include "dirsizeworker.h"
#include <QDebug>

DirSizeWorker::DirSizeWorker(QObject *parent)
    : Worker{parent}
{}

void DirSizeWorker::onRead(const QString &data)
{
    qDebug() << "NEW Worker:" << data;
}

В main.cpp заменим:

Thread *t = new Thread("cmd", params, new Runner(), new DirSizeWorker());

И добавим:

#include "dirsizeworker.h"

Запустим:

Инит
Запуск
Запущено
NEW Worker: "\r\nD:\\projects\\QtRunnerDemo3\\build\\Work_Desktop_Qt_6_9_1_shared_MinGW_w64_MINGW64_MSYS2-Debug>sleep 1 \r\n"
NEW Worker: "\r\nD:\\projects\\QtRunnerDemo3\\build\\Work_Desktop_Qt_6_9_1_shared_MinGW_w64_MINGW64_MSYS2-Debug>dir c:\\windows \r\n"
NEW Worker: " Том в устройстве C не имеет метки.\r\n Серийный номер тома: 5A32-0ABC\r\n\r\n Содержимое папки c:\\windows\r\n\r\n"
NEW Worker: "21.07.2025 12:42    <DIR>          .\r\n"
NEW Worker: "21.07.2025 12:42    <DIR>          ..\r\n"
…
NEW Worker: "07.12.2019 20:14    <DIR>          WaaS\r\n07.12.2019  20:31   <DIR>          Web\r\n"
NEW Worker: "12.06.2025 13:31               167 win.ini\r\n04.08.2025  13:11               276 WindowsUpdate.log\r\n"
NEW Worker: "07.12.2019 20:10            11 776 winhlp32.exe\r\n13.07.2025  12:11   <DIR>         WinSxS\r\n"
NEW Worker: "08.12.2019 01:39           316 640 WMSysPr9.prx\r\n07.12.2019  08:29            11 264 write.exe\r\n"
NEW Worker: "              31 файлов     12 678 753 байт\r\n"
NEW Worker: "             79 папок   8 068 255 744 байт свободно\r\n"
Код завершения 0
Статус выхода QProcess::NormalExit
Worker: Внешнаяя программа успешно завершилась с кодом: 0 и статусом  QProcess::NormalExit

Теперь вместо метода из родительского класса Worker используется метод из наследованного класса DirSizeWorker.

Разбор (парсинг) результата запуска внешней программы с помощью регулярных выражений

Для разбора(парсинга) текстовых данных самым эффективным способом является использование регулярных выражений. Код для работы с ними уже максимально оптимизирован, поэтому вы не получите значительного выигрыша в производительности используя прочие методы.

Допустим, нам нужно разобрать результат вывода команды dir c:\windows и получить общий размер файлов в папке.

Данные всегда выводятся в самом конце вывода данной команды и выглядят так:

        31 файлов     12 671 361 байт
        79 папок   8 120 934 400 байт свободно 

В начале каждой сроки находится некоторое количество пробелов.

Напишем регулярное выражение для строки:

        31 файлов     12 671 361 байт

У нас получится:

\s*(?<files>\d*)\s*файлов\s*(?<foldersize>[\d\xa0]*)\sбайт

В рамках этой статьи я не буду рассматривать само регулярное выражение, это тема для отдельной статьи.

Данное выражение при наличии совпадений помещает в группы результатов отдельно количество фалов и размер папки и у нас получается 

files: 31
foldersize: 12 671 361

Напишем класс для работы с регулярными выражениями:

Создадим класс RegexParser

Заголовок:

#ifndef REGEXPARSER_H
#define REGEXPARSER_H

#include <QObject>
#include <QRegularExpression>
#include <QString>


class RegexParser : public QObject
{
    Q_OBJECT
signals:
    void matched(QString data);

public:
    explicit RegexParser(const QString &pattern);
    QString parseGroup(QString data, QString group);
    void setPattern(QString pattern);

private:
    QString pattern;
    QRegularExpression *regularExp;
};

#endif // REGEXPARSER_H

Реализация:

#include "regexparser.h"
#include <QRegularExpression>

RegexParser::RegexParser(const QString &pattern)
{
   this->setPattern(pattern);
}

QString RegexParser::parseGroup(QString data, QString group)
{   
   QRegularExpressionMatchIterator i = this->regularExp->globalMatch(data);

   if(!this->regularExp->isValid())
    {
        qDebug() << "ERROR: "<< this->regularExp->errorString();
        exit(EXIT_FAILURE);
    }

    while (i.hasNext()) {
       QRegularExpressionMatch next = i.next();
            qDebug() << ">>" << next.captured(group);
            return next.captured(group);
    }

    return QString();
}

void RegexParser::setPattern(QString pattern)
{
    this->pattern = pattern;
   this->regularExp = new QRegularExpression(pattern);
}

Метод parseGroup производит поиск в строке data и возвращает результат соответствующий первой найденной группе group.

Внесем изменения в класс DirSizeWorker

Приведем метод onRead к виду:

void DirSizeWorker::onRead(const QString &data)
{
    qDebug() << "NEW Worker:" << data;

    RegexParser p = RegexParser("\\s*(?<files>\\d*)\\s*файлов\\s*(?<foldersize>[\\d\\xa0]*)\\sбайт");

    QString files = p.parseGroup(data,"files");
    QString foldersize = p.parseGroup(data,"foldersize");

    if (!files.isEmpty() && !foldersize.isEmpty())
    {
        qDebug() << files;
        qDebug() << foldersize;
    }
}

Обратите внимание, так как мы пишем на C++ мы обязаны в строках все символы \ экранировать с помощь дополнительного символа \.

В результате регулярное выражение у нас принимает вид:

\\s*(?<files>\\d*)\\s*файлов\\s*(?<foldersize>[\\d\\xa0]*)\\sбайт

Запустим:

NEW Worker: "13.07.2025 12:11    <DIR>          WinSxS\r\n"
NEW Worker: "08.12.2019 01:39           316 640 WMSysPr9.prx\r\n"
NEW Worker: "07.12.2019 08:29            11 264 write.exe\r\n"
NEW Worker: "              31 файлов     12 688 609 байт\r\n"
>> "31"
>> "12 688 609"
"31"
"12 688 609"
NEW Worker: "              79 папок   7 497 076 736 байт свободно\r\n"

Мы получили количество файлов и размер. Правда все данные у нас в текстовом формате, к тому же нам нужно удалить лишние пробелы в строке с данными о размере папки.

Для этого добавим в класс RegexParser новый метод:

    QString replace(QString data, const QString &replace);

Реализация:

QString RegexParser::replace(QString data, const QString &replace)
{
    QRegularExpression re(this->pattern);

   data.replace(re,replace);

    return data;
}

Используем добавленный метод в классе DirSizeWorker:

void DirSizeWorker::onRead(const QString &data)
{
    qDebug() << "NEW Worker:" << data;

    RegexParser p = RegexParser("\\s*(?<files>\\d*)\\s*файлов\\s*(?<foldersize>[\\d\\xa0]*)\\sбайт");

    QString files = p.parseGroup(data,"files");
    QString foldersize = p.parseGroup(data,"foldersize");

    if (!files.isEmpty() && !foldersize.isEmpty())
    {
        qDebug() << files;
        qDebug() << foldersize;

       p.setPattern("\\s");
        QString folderFreeSizeNumber = p.replace(foldersize,"");

        qDebug() << "Число: " << folderFreeSizeNumber;
    }
}

Запустим:

NEW Worker: "              31 файлов     12 688 609 байт\r\n"
>> "31"
>> "12 688 609"
"31"
"12 688 609"
Число: "12 688 609"

Число не изменилось!

Всё дело в том, что для вывода таких чисел в Windows команда dir использует неразрываемый пробел (Non-breaking space).

Изменим наше регулярное выражение на:

   p.setPattern("\\xa0");

Запустим снова:

>> "31"
>> "12 688 609"
"31"
"12 688 609"
Число: "12688609"

Теперь у нас есть целое число, но оно все еще представляет собой строку. Переведем эту строку в число и сразу переведем байты в мегабайты, чтобы сделать результат более читаемым:

После:

qDebug() << "Число: " << folderFreeSizeNumber;

Добавим:

bool isOk = false;
double folderFreeSizeMb = folderFreeSizeNumber.toDouble(&isOk);
qDebug() << "isOk" << isOk;
qDebug() << folderFreeSizeMb;
       
int diskMbSize = folderFreeSizeMb / 1024 / 1024;
qDebug() << diskMbSize << "Mb";

Запустим:

>> "31"
>> "12 688 609"
"31"
"12 688 609"
Число: "12688609"
isOk true
1.26886e+07
12 Mb

Обратите внимание, если вы используете тип int или long и размер папки будет больше чем может вместить переменная заданного типа, то получите результат:

>> "31"
>> "12 688 609"
"31"
"12 688 609"
Число:  "12688609"
isOk false
0
0 Mb

Имейте это ввиду и при необходимости обрабатывайте результат приведения типов в переменной isOk!

В результате у нас получится:

Класс RegexParser:

Заголовок:

#ifndef REGEXPARSER_H
#define REGEXPARSER_H

#include <QObject>
#include <QRegularExpression>
#include <QString>


class RegexParser : public QObject
{
    Q_OBJECT
signals:
    void matched(QString data);

public:
    explicit RegexParser(const QString &pattern);
    QString parseGroup(QString data, QString group);
    void setPattern(QString pattern);
    QString replace(QString data, const QString &replace);

private:
    QString pattern;
    QRegularExpression *regularExp;
};

#endif // REGEXPARSER_H

Реализация:

#include "regexparser.h"
#include <QRegularExpression>

RegexParser::RegexParser(const QString &pattern)
{
    this->setPattern(pattern);
}

QString RegexParser::parseGroup(QString data, QString group)
{   
    QRegularExpressionMatchIterator i = this->regularExp->globalMatch(data);

    if(!this->regularExp->isValid())
    {
        qDebug() << "ERROR: "<< this->regularExp->errorString();
        exit(EXIT_FAILURE);
    }

    while (i.hasNext()) {
        QRegularExpressionMatch next = i.next();
            qDebug() << ">>" << next.captured(group);
            return next.captured(group);
    }

    return QString();
}

void RegexParser::setPattern(QString pattern)
{
    this->pattern = pattern;
    this->regularExp = new QRegularExpression(pattern);
}

QString RegexParser::replace(QString data, const QString &replace)
{
    QRegularExpression re(this->pattern);

    data.replace(re,replace);

    return data;
}

Класс DirSizeWorker:

Заголовок:

#ifndef DIRSIZEWORKER_H
#define DIRSIZEWORKER_H

#include "worker.h"

class DirSizeWorker : public Worker
{
public:
    explicit DirSizeWorker(QObject *parent = nullptr);
    virtual void onRead(const QString &data);
};

#endif // DIRSIZEWORKER_H

Реализация:

#include "dirsizeworker.h"
#include "regexparser.h"
#include <QDebug>
#include <QRegularExpression>

DirSizeWorker::DirSizeWorker(QObject *parent)
    : Worker{parent}
{}

void DirSizeWorker::onRead(const QString &data)
{
    qDebug() << "NEW Worker:" << data;

    RegexParser p = RegexParser("\\s*(?<files>\\d*)\\s*файлов\\s*(?<foldersize>[\\d\\xa0]*)\\sбайт");

    QString files = p.parseGroup(data,"files");
    QString foldersize = p.parseGroup(data,"foldersize");

    if (!files.isEmpty() && !foldersize.isEmpty())
    {
        qDebug() << files;
        qDebug() << foldersize;

        p.setPattern("\\xa0");
        QString folderFreeSizeNumber = p.replace(foldersize,"");

        qDebug() << "Число: " << folderFreeSizeNumber;

        bool isOk = false;
        double folderFreeSizeMb = folderFreeSizeNumber.toDouble(&isOk);
        qDebug() << "isOk" << isOk;
        qDebug() << folderFreeSizeMb;

        int diskMbSize = folderFreeSizeMb / 1024 / 1024;
        qDebug() << diskMbSize << "Mb";

    }
}

Файл main.cpp

    Thread *t = new Thread("cmd", params, new Runner(), new DirSizeWorker());
    t->start();

Заключение

Сегодня мы рассмотрели разбор (парсинг) результатов работы запущенной внешней программы в Qt6:

Сделали метод класса Worker onRead виртуальным;

Создали класс DirSizeWorker, наследовали его от Woker и переопределили метод onRead в новом классе;

Заменили класс Worker на DirSizeWorker для использования при обработке результатов;

Создали регулярное выражение для разбора вывода команды dir;

Создали класс для разбора строк с помощью регулярных выражений RegexParser;

Использовали класс RegexParser в методе класса DirSizeWorker для разбора вывода внешней программы;

Привели результат разбора из формата строки к формату в числовом представлении;

Перевели результат из байтов в мегабайты.

>> Часть 6

Категория Qt6
Теги Qt Qt6 Cpp

Добавить комментарий

Простой текст

  • HTML-теги не обрабатываются и показываются как обычный текст
  • Строки и абзацы переносятся автоматически.
  • Адреса веб-страниц и email-адреса преобразовываются в ссылки автоматически.
Просмотров: 168