Разбор (парсинг) вывода программы с помощью регулярных выражений. Запуск внешних программ в 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 для разбора вывода внешней программы;
Привели результат разбора из формата строки к формату в числовом представлении;
Перевели результат из байтов в мегабайты.
Добавить комментарий