QProcess执行linux命令行的命令(详解各种情况可能存在的问题)

前言

因为我是做的linux下开发,所以程序中需要多次在qt中调用linux命令行的命令,并且需要根据命令的执行结果做出相应的判断。qt中使用QProcess类实现进程间通信,也就是说QProcess可以调用外部程序并获取外部程序的信息。linux中通过启动bash(windows中启动cmd)来执行命令行的命令,并可以获取命令行的返回信息。


下面我详细介绍一下QProcess的具体用法和我踩过的坑以及注意事项:

QProcess的重要方法:

start();                     //启动一个进程
close();                      //关闭启动的外部进程
write();                     //向外部进程写入数据
readAllStandardOutput();     //读取外部进程的标准输出
readAllStandardError();      //读取外部进程的错误信息
waitForStarted()			 //启动阻塞,等待程序启动完毕,期间整个程序所有进程阻塞
waitForFinished()			 //结束阻塞,等待程序结束完毕,期间整个程序所有进程阻塞

注意:QProcess中start()和write()中写的命令,末尾要加上\n(linux直接加\n,windows好像是\r\n,意思就是加个换行符),否则命令可能无法执行!!!


基础使用方法:

执行单条命令,且需要读取命令行返回信息时,代码如下:

	QProcess pro;
    connect(&pro,&QProcess::readyReadStandardOutput,this,[=,&pro]()mutable{ //注意我这里lambda表达式的写法,[]里的内容需要根据实际情况更改
        QString Output=pro.readAllStandardOutput();
        qDebug()<<"Output:"<<Output;
    });
    connect(&pro,&QProcess::readyReadStandardError,this,[=,&pro]()mutable{ 
        QString Error=pro.readAllStandardError();
        qDebug()<<"Error:"<<pro.readAllStandardError();
    });

    pro.start("bash");			//在start中写命令,可将括号里的内容直接换成命令
    pro.waitForStarted();       //阻塞,等待bash启动完毕
	pro.waitForFinished();
	pro.close();				//在适当的位置关闭外部程序

优化

上面的代码是网上普遍给的使用方式,直接在start方法中写命令方便好用。但如果我想同时执行多条命令,且读出每次执行完命令的返回结果,这是一个QProcess对象就只能start一次。所以就必须把命令写在write()方法中,优化后的代码如下

QProcess pro;
    connect(&pro,&QProcess::readyReadStandardOutput,this,[=,&pro]()mutable{ //注意我这里lambda表达式的写法,[]里的内容需要根据实际情况更改
        QString Output=pro.readAllStandardOutput();
        qDebug()<<"Output:"<<Output;
    });
    connect(&pro,&QProcess::readyReadStandardError,this,[=,&pro]()mutable{ //注意我这里lambda表达式的写法,[]里的内容需要根据实际情况更改
        QString Error=pro.readAllStandardError();
        qDebug()<<"Error:"<<pro.readAllStandardError();
    });

    pro.start("bash");
    pro.waitForStarted();       //阻塞,等待bash启动完毕
    pro.write("ls /root \n");
    delayMSec(200);     //非阻塞延迟给命令足够的执行时间,时间需根据命令的执行速度而定

	QString path="/";
    QString ls_command=QString("ls %1\n").arg(path);    //执行带有变量的命令
    pro.write(ls_command.toLocal8Bit().data());
    delayMSec(200);

	pro.close();				//在适当的位置关闭外部程序

非阻塞延迟delayMSec():–自己封装的

void MainWindow::delayMSec(unsigned int msec)
{
    QTime Time_set = QTime::currentTime().addMSecs(msec);
    while( QTime::currentTime() < Time_set )
        QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
}

注意:write()方法不可与waitForFinished()一起使用,否则会阻塞30秒。waitForFinished()只能用start()一起使用


什么是阻塞?很重要!!!

我查了很多的资料,网上的博客基本都是把阻塞一笔带过,但我在执行复杂的调用linux命令时,发现阻塞才是决定程序能否正确执行的关键。

  • 阻塞:waitForStarted和waitForFinished,其实在执行外部命令时,也是需要花时间的,比如开启防火墙大概需要200ms,如果qt在执行开启防火墙操作,必须阻塞大概200ms,该命令才能执行成功,不然防火墙还没开启,qt就已经开始执行下一行代码了。读者可以尝试把waitForFinished加上和去掉对比一下
  • 非阻塞延迟delayMSec,这是我自己封装的方法,当写入多条命令时,如write1,write2…依然需要给每条命令执行的时间,我们所期望的是write1执行成功后再write2,write2成功后在执行后续的代码。但write和waitForFinished无法一起使用,所以只能自己添加延迟,但缺点是延迟的时间是手动设置的,无法确定延迟时间时,只能不断测试时间或者设置较长的时间
  • waitForFinished和非阻塞延迟区别:waitForFinished阻塞的时候,程序对外表现的时卡顿,程序中的所有线程都被阻塞掉了。比如我们想在阻塞的时候设置一个正在加载的动画提示,waitForFinished会导致动画不运行或者动画卡顿。我自己封装的非阻塞延迟delayMSec只会把程序执行的时间推迟,不会影响其他线程的运行。

我在阻塞上吃了很多亏,一遍一遍尝试代码,搜资料。


除了阻塞的地方是个坑外,qt中还无法识别管道“|”和重定向“>>”命令

解决方法:

	QProcess pro;
    pro.start("bash",QStringList() << "-c " << "ps -ef | grep firefox");       //执行带有管道的命令
    pro.waitForStarted();
    pro.waitForFinished();
    qDebug()<<"pro.readAll:"<<pro.readAll();

	pro.close();				//在适当的位置关闭外部程序

2022.8.8更新

pro.start("bash",QStringList() << "-c" << "ps -ef | grep firefox");       //执行带有管道的命令

注意:-c小写且前后没有空格,我之前的写法错了,导致一直无法运行,特此更新,注意注意!


原理是使用start的这个方法。意思是先打开一个程序,然后将字符数组的命令写到程序中,但不知道为什么要先加个-c,因为这里的命令是直接写到bash终端里的,所以末尾应该不用加\n

image-20220519150535373


取代QProcess的方法-syetem()方法

system()是linux的方法,使用起来比QProcess更方便,但无法获取命令行的返回值。这里有个致命的问题就是有的命令有没有执行成功是必须要看命令行打印的信息的,system只能返回一个int数值,一般来说返回值为0说明该条命令执行完毕了,但无法确定有没有执行成功

	system("ls");
	system("systemctl start firewalld.service");		//这里可以写任意数量的命令

注意:system()也是阻塞命令,会等到命令执行完毕才会结束阻塞


但如果我们既需要执行多条命令,又要程序的返回信息,且又想让程序阻塞时间达到最小,可以将命令都写在shell脚本里,然后调用脚本,这样只需一次start,阻塞的问题也能解决

qt代码:

    QProcess pro;
    connect(&pro,&QProcess::readyReadStandardOutput,this,[=,&pro]()mutable{ //注意我这里lambda表达式的写法,[]里的内容需要根据实际情况更改
        QString Output=pro.readAllStandardOutput();
        qDebug()<<"Output:"<<Output;
    });
    connect(&pro,&QProcess::readyReadStandardError,this,[=,&pro]()mutable{ 
        QString Error=pro.readAllStandardError();
        qDebug()<<"Error:"<<pro.readAllStandardError();
    });

    QString dirpath= QCoreApplication::applicationDirPath();        //----获取可执行文件所在的目录
    QString shell=QString(dirpath+"/test.sh %1 %2 %3\n").arg("/","/root","/home");	//解决传参问题
    pro.start(shell);			//执行脚本
    pro.waitForStarted();       //阻塞,等待bash启动完毕
    pro.waitForFinished();
    pro.close();

脚本中代码:

#!/bin/sh
	path1=$1
	path2=$2
	path3=$3
	ls ${path1}
	ls ${path2}
	ls ${path3}

执行结果:

img
使用脚本时记得给脚本文件添加可执行权限

chmod 777 test.sh

注意:最后还是想说一下使用阻塞会导致界面产生卡顿。想要解决这个问题自然要使用多线程,将阻塞代码在其他线程中运行即可。

链接:qt中Qthread线程的使用以及安全关闭

码字不易,如果这篇博客对你有帮助,麻烦点赞收藏,非常感谢!有不对的地方

你可能感兴趣的:(linux,qt,bash)