Разбор (парсинг) вывода результатов запущенной программы по строкам. Запуск внешних программ в Qt6. Часть 6
В прошлых частях мы рассмотрели разбор (парсинг) результатов работы запущенной программы, но при этом не рассматривали сами результаты вывода.
Сегодня мы рассмотрим построчный разбор (парсинг) вывода результата работы внешней программы в Qt6.
Оптимизация класса DirSizeWorker
Первым делом оптимизируем класс DirSizeWorker.
private:
RegexParser *parser;Приведем метод onRead к виду:
void DirSizeWorker::onRead(const QString &data)
{
qDebug() << "NEW Worker:" << data;
QString files = this->parser->parseGroup(data,"files");
QString foldersize = this->parser->parseGroup(data,"foldersize");
if (!files.isEmpty() && !foldersize.isEmpty())
{
qDebug() << files;
qDebug() << foldersize;
RegexParser p = RegexParser("\\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";
}
}Таким образом мы убрали лишний вызов конструктора RegexParser для каждой обрабатываемой строки.
Разбираем (парсим) вывод программы по строкам
Если мы обратим внимание на результат запуска, то увидим следующее:
Worker: "07.12.2019 20:31 <DIR> SKB\r\n29.07.2025 08:55 <DIR> SoftwareDistribution\r\n"
Worker: "07.12.2019 20:14 <DIR> Speech\r\n07.12.2019 20:14 <DIR> Speech_OneCore\r\n"
Worker: "12.07.2025 13:30 164 352 splwow64.exe\r\n"
Worker: "07.12.2019 20:14 <DIR> System\r\n"Обратите внимание на
\r\nЭто символы перехода на новую строку.
На самом деле вместо:
07.12.2019 20:31 <DIR> SKB\r\n29.07.2025 08:55 <DIR> SoftwareDistribution\r\n
07.12.2019 20:14 <DIR> Speech\r\n07.12.2019 20:14 <DIR> Speech_OneCore\r\n
12.07.2025 13:30 164 352 splwow64.exe\r\n
07.12.2019 20:14 <DIR> System\r\nНам нужно :
07.12.2019 20:31 <DIR> SKB\r\n
29.07.2025 08:55 <DIR> SoftwareDistribution\r\n
07.12.2019 20:14 <DIR> Speech\r\n
07.12.2019 20:14 <DIR> Speech_OneCore\r\n
12.07.2025 13:30 164 352 splwow64.exe\r\n
07.12.2019 20:14 <DIR> System\r\nТ.е. нам нужно озаботится нормальной разбивкой на строки.
В одной из прошлых задач мы отметили, что QProcess выводит данные построчно, это на самом деле так, но в зависимости от производительности ПК или загруженности системы, он может склеивать несколько строк.
Чтобы перейти к построчной обработке вывода, нам нужно удостоверится, что при обработке полученных данных, они будут разбиты на строки.
Разбиваем вывод программы на строки с помощью регулярных выражений
В класс RegexParser добавим новый метод:
void parseAll(QString data);Реализация:
void RegexParser::parseAll(QString data)
{
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();
for (int var = 1; var < next.capturedTexts().count(); ++var) {
QString cap = next.captured(var);
if (!cap.trimmed().isEmpty())
{
qDebug() << cap.trimmed();
}
}
}
}Здесь мы в цикле проходим по всем совпадениям, которые были выявлены нашим регулярным выражением.
Обратите внимание, что мы обрабатываем элементы с индекса 1, это необходимо из-за того, что в элемент с индексом 0 помещается вся исходная строка, в которой было обнаружено совпадение!
С помощью:
cap.trimmed()Мы удаляем лишние пробелы в начале и конце и переносы строк.
Временно вернемся к использованию старого класса Worker, для этого в main.cpp заменим:
Thread *t = new Thread("cmd", params, new Runner(), new Worker());В класс Worker в начало метода onRead добавим:
this->parser->parseAll(data);Добавим приватное поле:
private:
RegexParser *parser;В конструктор класса добавим:
this->parser = new RegexParser("(.[^\\r\\n]*)(?:\\n|\\r\\n)?");Запустим.
Обратите внимание, что теперь в строках вывода, содержащих символы перехода на новую строку, наш парсер выводит две отдельные строки:
"07.10.2024 10:27 764 688 pyw.exe"
"18.05.2024 13:45 370 176 regedit.exe"
Worker: "07.10.2024 10:27 764 688 pyw.exe\r\n18.05.2024 13:45 370 176 regedit.exe"Приведем метод onRead к первоначальному виду:
void Worker::onRead(const QString &data)
{
qDebug() << "Worker:" << data;
}И удалим поле класса и добавленную строку из конструктора класса Worker.
Создаем дочерний класс ReadLineWorker
Разбивка по строкам это одна из задач разбора результатов, они могу быть и в CSV формате, в XML, HTML или JSON. Поэтому оставим класс Worker базовым и создадим отдельный класс, для разбора вывода по строкам:
Заголовок:
#ifndef READLINEWORKER_H
#define READLINEWORKER_H
#include <QObject>
#include "worker.h"
#include "regexparser.h"
class ReadLineWorker: public Worker
{
Q_OBJECT
public:
explicit ReadLineWorker(QObject *parent = 0);
virtual void onRead(const QString &data);
private:
RegexParser *parser;
public slots:
void onStringRead(QString data);
};
#endif // READLINEWORKER_HРеализация:
#include "readlineworker.h"
ReadLineWorker::ReadLineWorker(QObject *parent)
{
this->parser = new RegexParser("(.[^\\r\\n]*)(?:\\n|\\r\\n)?");
QObject::connect(this->parser, SIGNAL(matched(QString)),
this, SLOT(onStringRead(QString)));
}
void ReadLineWorker::onRead(const QString &data)
{
this->parser->parseAll(data);
// qDebug() << "Worker:" << data;
}
void ReadLineWorker::onStringRead(QString data)
{
qDebug() << "Worker String" << data;
}В main.cpp заменим:
Thread *t = new Thread("cmd", params, new Runner(), new ReadLineWorker());Запустим, результат тот же.
Для того чтобы эффективно обрабатывать построчный вывод, нам нужно иметь возможность делать это для каждой поступающей строки. Сделать это можно с помощью сигнала.
В класс RegexParser добавим сигнал:
signals:
void matched(QString data);В методе parseAll класса RegexParser заменим:
qDebug() << cap.trimmed(); На:
emit matched(cap.trimmed());Теперь каждый раз, при успешном обнаружении новой строки, будет отправляться сигнал.
Соединим сигнал парсера со слотом нашего класса, для этого в конструктор класса ReadLineWorker добавим:
QObject::connect(this->parser, SIGNAL(matched(QString)),
this, SLOT(onStringRead(QString)));В класс ReadLineWorker добавим слот:
private slots:
void onStringRead(QString data);Реализация:
void Worker::onStringRead(QString data)
{
qDebug() << "Worker String" << data;
}В методе onRead закомментируем строку:
qDebug() << "Worker:" << data;Запустим:
Worker String "D:\\projects\\QtRunnerDemo3\\build\\Work_Desktop_Qt_6_9_1_shared_MinGW_w64_MINGW64_MSYS2-Debug>dir"
Worker String "c:\\windows\\"
Worker String "Том в устройстве C не имеет метки."
Worker String "Серийный номер тома: 5A32-0ABC"
Worker String "Содержимое папки c:\\windows"
Worker String "21.07.2025 12:42 <DIR> ."
Worker String "21.07.2025 12:42 <DIR> .."
Worker String "08.12.2019 01:37 <DIR> addins"
Worker String "15.11.2023 13:57 <DIR> ADFS"
Worker String "21.10.2024 17:10 <DIR> appcompat"
Worker String "22.01.2025 14:30 <DIR> apppatch"
Worker String "06.08.2025 15:52 <DIR> AppReadiness"
Worker String "12.07.2025 14:06 <DIR> bcastdvr"
Worker String "19.02.2025 17:40 93 696 bfsvc.exe"
Worker String "16.04.2024 16:42 <DIR> Boot"
Worker String "07.12.2019 20:14 <DIR> Branding"
…
Worker String "07.08.2025 11:10 276 WindowsUpdate.log"
Worker String "07.12.2019 20:10 11 776 winhlp32.exe"
Worker String "13.07.2025 12:11 <DIR> WinSxS"
Worker String "08.12.2019 01:39 316 640 WMSysPr9.prx"
Worker String "07.12.2019 08:29 11 264 write.exe"
Worker String "31 файлов 12 739 689 байт"
Worker String "79 папок 6 070 812 672 байт свободно"
Worker: Внешняя программа успешно завершилась с кодом: 0 и статусом QProcess::NormalExitТеперь для каждой найденной парсером строки будет отправляться сигнал и вызываться слот onStringRead.
Теперь вывод корректно разбивается по строкам и его проще обрабатывать!
Модернизируем класс DirSizeWorker
У нас есть новый базовый класс для разбивки результатов по строкам, сделаем класс ReadLineWorker родительским для класса DirSizeWorker.
Для того, чтобы класс DirSizeWorker мог получать доступ к строкам, нам потребуется отправлять сигнал этому классу из класса ReadLineWorker.
В класс ReadLineWorker добавим сигнал:
signals:
void stringRead(QString data);Изменим слот:
void ReadLineWorker::onStringRead(QString data)
{
emit stringRead(data);
}Теперь при получении новой строки мы просто отправляем новый сигнал.
В результате класс DirSizeWorker изменится:
#ifndef DIRSIZEWORKER_H
#define DIRSIZEWORKER_H
#include "readlineworker.h"
class DirSizeWorker : public ReadLineWorker
{
public:
explicit DirSizeWorker(QObject *parent = nullptr);
public slots:
void onStringReady(const QString &data);
private:
RegexParser *parser;
};
#endif // DIRSIZEWORKER_HРеализацию:
#include "dirsizeworker.h"
#include <QDebug>
DirSizeWorker::DirSizeWorker(QObject *parent)
: ReadLineWorker{parent}
{
this->parser = new RegexParser("\\s*(?<files>\\d*)\\s*файлов\\s*(?<foldersize>[\\d\\xa0]*)\\sбайт");
QObject::connect(this, ReadLineWorker::stringRead,
this, DirSizeWorker::onStringReady);
}
void DirSizeWorker::onStringReady(const QString &data)
{
qDebug() << "NEW Worker:" << data;
QString files = this->parser->parseGroup(data,"files");
QString foldersize = this->parser->parseGroup(data,"foldersize");
if (!files.isEmpty() && !foldersize.isEmpty())
{
qDebug() << files;
qDebug() << foldersize;
RegexParser p = RegexParser("\\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";
}
}
Мы связали сигнал stringRead родительского класса и слот дочернего.
В main.cpp изменим:
Thread *t = new Thread("cmd", params, new Runner(), new DirSizeWorker());Запустим:
NEW Worker: "D:\\projects\\QtRunnerDemo3\\build\\Work_Desktop_Qt_6_9_1_shared_MinGW_w64_MINGW64_MSYS2-Debug>"
NEW Worker: "dir"
NEW Worker: "c:\\windows\\"
NEW Worker: "Том в устройстве C не имеет метки."
NEW Worker: "Серийный номер тома: 5A32-0ABC"
NEW Worker: "Содержимое папки c:\\windows"
NEW Worker: "21.07.2025 12:42 <DIR> ."
NEW Worker: "21.07.2025 12:42 <DIR> .."
NEW Worker: "08.12.2019 01:37 <DIR> addins"
NEW Worker: "15.11.2023 13:57 <DIR> ADFS"
NEW Worker: "21.10.2024 17:10 <DIR> appcompat"
…
>> "31"
>> "12 739 689"
"31"
"12 739 689"
Число: "12739689"
isOk true
1.27397e+07
12 Mb
NEW Worker: "79 папок 6 061 297 664 байт свободно"
Worker: Внешнаяя программа успешно завершилась с кодом: 0 и статусом QProcess::NormalExitМы избавились от лишних пробелов и переносов строк и теперь можем обрабатывать результаты построчно.
Заключение
Сегодня мы рассмотрели построчный разбор (парсинг) вывода результата работы внешней программы в Qt6:
Оптимизировали класс DirSizeWorker;
Доработали класс RegexParser добавив в него новый сигнал, отправляемый каждый раз, когда обнаружено совпадение;
Создали класс ReadLineWorker для работы с разбивкой вывода на строки;
Наследовали класс DirSizeWorker от ReadLineWorker.
Добавить комментарий