# Linux C++运行shell命令增加超时机制

# 背景

在开发过程中,很多时候会用编程调用系统shell命令行运行一些第三方程序,然后获取输出。然而有些命令执行时间很长或者卡住,这样会导致程序无法继续下一步逻辑,所以需要一个超时机制去检查,如果超时了杀死命令进程。

这是俺使用的工具函数,创建指定命令行执行,然后获取输出,同时可以指定超时时间。

主要是通过select机制获取输出同时判断是否超时

#include <iostream>
#include <sys/wait.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>


using namespace std;


/**
 * @brief 执行shell命令
 * @param cmd 命令
 * @param argv 参数列表
 * @param result 运行输出
 * @param timeout 运行超时时间(秒)
 * @return 命令的退出代码
*/
int RunProcess(const string& cmd, char* const argv[], string& result, int timeout)
{
    int fd_out[2];
    int fd_err[2];
    int status = -1;
    pipe(fd_out);
    pipe(fd_err);
    pid_t pid = fork();
    if (pid == 0) {
        close(fd_out[0]);
        close(fd_err[0]);
        dup2(fd_out[1], STDOUT_FILENO);
        dup2(fd_err[1], STDERR_FILENO);
        execvp(cmd.c_str(), argv);
        // execvp调用失败,直接返回-1
        perror("execvp error");
        exit(-1);
    }
    else if (pid > 0) {
        close(fd_out[1]);
        close(fd_err[1]);
        char buffer[1024] = { 0 };
        fd_set readSet;
        
        time_t startTime = time(NULL),curTime = 0;
        while (1)
        {
            FD_ZERO(&readSet);
            FD_SET(fd_out[0], &readSet);
            FD_SET(fd_err[0], &readSet);
            int fdMax = max(fd_out[0], fd_err[0]);
            struct timeval tv = { 1, 0 };  // 1 seconds, 0 microseconds;

            curTime = time(NULL);

            if (timeout != -1 && (curTime - startTime > timeout))
            {
                printf("exec timeout!\n");
                kill(pid, SIGTERM);
                if (waitpid(pid, &status, 0) > 0)//阻塞等待进程退出了
                {
                    printf("child exit %d\n", status);
                }
                break;
            }

            int res = select(fdMax + 1, &readSet, NULL, NULL, &tv);
            if (res < 0) {
                //printf("timeout\n");
                if (errno != EINTR)
                {
                    printf("select() error %d %s\n", errno, strerror(errno));
                }
                break;//错误
            }
            else if (res == 0)
            {
                //超时,继续等待
                
                if (waitpid(pid, &status, WNOHANG) > 0)//进程退出了
                {
                    printf("child exit %d\n", status);
                    break;
                }

                continue;
            }


            if (FD_ISSET(fd_out[0], &readSet))//输出有数据了
            {
                int len = 0;
readmore:
                len = read(fd_out[0], buffer, sizeof(buffer));
                if (len > 0)
                {
                  
                    result.append(buffer, len);
                    if (len == sizeof(buffer))
                    {
                        goto readmore;
                    }
                }
                else {
                    printf("read error %d %s\n", len, strerror(errno));
                    if (waitpid(pid, &status, WNOHANG) > 0)//进程退出了
                    {
                        printf("child exit %d\n", status);
                        break;
                    }
                    break;
                }
               
            }
            if (FD_ISSET(fd_err[0], &readSet))//输出有数据了
            {
                int len = 0;
readmore1:
                len = read(fd_err[0], buffer, sizeof(buffer));
                if (len > 0)
                {
                    
                    result.append(buffer, len);
                    if (len == sizeof(buffer))
                    {
                        goto readmore1;
                    }
                }
               else {
                    printf("read error %d %s\n", len, strerror(errno));
                    if (waitpid(pid, &status, WNOHANG) > 0)//进程退出了
                    {
                        printf("child exit %d\n", status);
                        break;
                    }
                    break;
                }
            }
        }



        close(fd_err[0]);
        close(fd_out[0]);
    }

   //linux有些命令执行成功,状态码不一定是0,需要根据输出来判断
    return status;

}

int main(int argc, char* argv[])
{
    //参数格式,{程序名称,参数1,...,参数n,NULL},相当于main函数的argv参数
    char* const param[] = { "ping","192.168.3.1",NULL };//必须以NULL结束
    string resStr;
    int status = RunProcess("ping", param, resStr,10);//超时10秒
    printf("status:%d restult:%s\n",status,resStr.c_str());
}

编译运行

gcc -o runcmd main.cpp -lstdc++
./runcmd

程序运行ping 192.168.3.1命令,由于在linux ping命令默认是一直执行,这样可以模拟命令执行过久的场景,10秒后超时返回

Last Updated: 2/3/2024, 3:26:33 PM