關于管道——子進程調用命令行并返回執行結果的問題
Posted on 2010-03-05 19:22 David Fang 閱讀(3394) 評論(3) 編輯 收藏 引用 所屬分類: C/C++系統編程 今天碰到一個有意思的問題,如何在GUI里調用命令行并返回處理結果信息。說是在GUI程序里調用,其實和普通進程調用命令行并返回結果的原理是一樣的。一般情況下,用system或CreateProcess創建一個進程來執行一個命令行,并不關心它執行過程中的具體信息,有時甚至不在乎它何時結束,而在今天所碰到的問題中,卻恰恰需要關注這兩點。關于這兩點,可以再強調一下:
1.在一個進程中(GUI進程或普通的Console進程,原理都是一樣的)調用一個指定的命令行,并等待其返回,也就是說調用過程是阻塞的,不然沒辦法搜取到準確的信息。
2.調用完畢后必須得到這個命令行調用過程中的輸出信息,即單獨在控制臺上執行該命令行所輸出的信息。
好了,這就是我們的需求。我們主函數看起來是這個樣子的:
cmd_caller類做些什么?簡單的說就是創建一個進程,然后讓該進程執行給定的命令,最后通過某種方式返回結果,而這種方式就是我們所說的管道。我們先看看Windows系統上的實現,這個實現是基于網上的一篇文章。
這個是cmd_caller的類申明,cmd_caller_private_data結構便于跨平臺實現(具體請看后面):
下面是cmd_caller在Windows上的一種實現:
下面我們再來看一下Linux下的實現:
到這里時,任務已近完成,然而,還有一種更簡單的調用方法,就是popen/pclose函數(Windows上是_popen/_pclose函數),下面就是利用這種方法的實現:
同樣,在Windows上 :
值得注意的是,以上兩種方法有下面一些不同:
1.第一種實現直接將子進程的標準輸出和錯誤輸出重定向到管道,而第二種實現卻需要在命令中指出重定向。
2.調用方式。如果命令是一個路徑,并且路徑中間有特殊字符如空格,那么兩種實現的調用方法可能有些區別,前一種方式更直接,而后一種方式需要加轉義。最后以Windows上的一個例子來說明這兩種調用之間的區別:
以第一種實現調用
1.在一個進程中(GUI進程或普通的Console進程,原理都是一樣的)調用一個指定的命令行,并等待其返回,也就是說調用過程是阻塞的,不然沒辦法搜取到準確的信息。
2.調用完畢后必須得到這個命令行調用過程中的輸出信息,即單獨在控制臺上執行該命令行所輸出的信息。
好了,這就是我們的需求。我們主函數看起來是這個樣子的:
1 /* Main.cpp */
2 #include "cmd_caller.h"
3 #include <iostream>
4
5 int main(int argc, char* argv[])
6
7 {
8 cmd_caller caller("netstat -ano");
9 std::string retstring;
10 if (!caller.call_cmd(retstring))
11 {
12 std::cout<<"call failed\n";
13 return 1;
14 }
15 std::cout.write(retstring.c_str(), retstring.size());
16 return 0;
17 }
2 #include "cmd_caller.h"
3 #include <iostream>
4
5 int main(int argc, char* argv[])
6
7 {
8 cmd_caller caller("netstat -ano");
9 std::string retstring;
10 if (!caller.call_cmd(retstring))
11 {
12 std::cout<<"call failed\n";
13 return 1;
14 }
15 std::cout.write(retstring.c_str(), retstring.size());
16 return 0;
17 }
cmd_caller類做些什么?簡單的說就是創建一個進程,然后讓該進程執行給定的命令,最后通過某種方式返回結果,而這種方式就是我們所說的管道。我們先看看Windows系統上的實現,這個實現是基于網上的一篇文章。
這個是cmd_caller的類申明,cmd_caller_private_data結構便于跨平臺實現(具體請看后面):
1 /* cmd_caller.h */
2 #ifndef __CMD_CALLER_H__
3 #define __CMD_CALLER_H__
4 #include <string>
5
6 struct cmd_caller_private_data;
7 class cmd_caller
8 {
9 public:
10 cmd_caller(const char* cmdstr);
11 cmd_caller(const std::string& cmdstr);
12 virtual ~cmd_caller();
13 void set_cmd(const char* cmdstr);
14 void set_cmd(const std::string& cmdstr);
15 bool call_cmd(std::string& output);
16 protected:
17 private:
18 bool init_private_data();
19 cmd_caller_private_data* private_data_;
20 };
21 #endif
2 #ifndef __CMD_CALLER_H__
3 #define __CMD_CALLER_H__
4 #include <string>
5
6 struct cmd_caller_private_data;
7 class cmd_caller
8 {
9 public:
10 cmd_caller(const char* cmdstr);
11 cmd_caller(const std::string& cmdstr);
12 virtual ~cmd_caller();
13 void set_cmd(const char* cmdstr);
14 void set_cmd(const std::string& cmdstr);
15 bool call_cmd(std::string& output);
16 protected:
17 private:
18 bool init_private_data();
19 cmd_caller_private_data* private_data_;
20 };
21 #endif
下面是cmd_caller在Windows上的一種實現:
1 #include "cmd_caller.h"
2 #include <Windows.h>
3 #include <stdio.h>
4
5 struct cmd_caller_private_data
6 {
7 std::string cmd_str;
8 HANDLE hPipeRead;
9 HANDLE hPipeWrite;
10 SECURITY_ATTRIBUTES sa;
11 STARTUPINFOA si;
12 PROCESS_INFORMATION pi;
13 };
14
15 cmd_caller::cmd_caller(const char* cmdstr)
16 {
17 private_data_ = new cmd_caller_private_data();
18 private_data_->cmd_str = std::string(cmdstr);
19 }
20
21 cmd_caller::cmd_caller(const std::string& cmdstr)
22 {
23 private_data_ = new cmd_caller_private_data();
24 private_data_->cmd_str = cmdstr;
25 }
26
27 bool cmd_caller::init_private_data()
28 {
29 private_data_->sa.nLength = sizeof(SECURITY_ATTRIBUTES);
30 private_data_->sa.lpSecurityDescriptor = NULL;
31 private_data_->sa.bInheritHandle = TRUE;
32
33 if (!CreatePipe(&private_data_->hPipeRead,
34 &private_data_->hPipeWrite,
35 &private_data_->sa,
36 0)) //創建管道)
37 {
38 return false;
39 }
40
41
42 private_data_->si.cb = sizeof(STARTUPINFOA);
43 GetStartupInfo(&private_data_->si); //設置啟動信息
44 private_data_->si.hStdError = private_data_->hPipeWrite; //將子進程錯誤輸出結果寫到管道
45 private_data_->si.hStdOutput = private_data_->hPipeWrite; //將子進程標準輸出結果寫出管道
46 private_data_->si.wShowWindow = SW_HIDE; //隱式調用子程序,及不顯示子程序窗口
47 private_data_->si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
48
49 return true;
50 }
51
52 cmd_caller::~cmd_caller()
53 {
54 delete private_data_;
55 }
56
57 void cmd_caller::set_cmd(const char* cmdstr)
58 {
59 private_data_->cmd_str = std::string(cmdstr);
60 }
61
62 void cmd_caller::set_cmd(const std::string& cmdstr)
63 {
64 private_data_->cmd_str = cmdstr;
65 }
66
67 bool cmd_caller::call_cmd(std::string& output)
68 {
69 if(!init_private_data())
70 {
71 return false;
72 }
73
74 //根據設定的參數創建并執行子進程
75 if(!CreateProcessA(NULL,
76 (LPSTR)private_data_->cmd_str.c_str(),
77 NULL,
78 NULL,
79 TRUE,
80 NULL,
81 NULL,
82 NULL,
83 &private_data_->si,
84 &private_data_->pi))
85 {
86 return false;
87 }
88
89 //等待子進程退出
90 if(WAIT_FAILED == WaitForSingleObject(private_data_->pi.hProcess, INFINITE))
91 {
92 return false;
93 }
94
95 //關閉子進程的相關句柄,釋放資源
96 CloseHandle(private_data_->pi.hProcess);
97 CloseHandle(private_data_->pi.hThread);
98
99 //Note:We must close the write handle of pipe, else the ReadFile call will pending infinitely.
100 CloseHandle(private_data_->hPipeWrite);
101
102 char buf[1024];
103 DWORD dwRead;
104 output.clear();
105
106 for (;;)
107 {
108 BOOL result = ReadFile(private_data_->hPipeRead, buf, 1024, &dwRead, NULL);
109
110 if(!result || dwRead == 0)
111 {
112 break;
113 }
114 else
115 {
116 output.append(buf, dwRead);
117 }
118 }
119 return true;
120 }
2 #include <Windows.h>
3 #include <stdio.h>
4
5 struct cmd_caller_private_data
6 {
7 std::string cmd_str;
8 HANDLE hPipeRead;
9 HANDLE hPipeWrite;
10 SECURITY_ATTRIBUTES sa;
11 STARTUPINFOA si;
12 PROCESS_INFORMATION pi;
13 };
14
15 cmd_caller::cmd_caller(const char* cmdstr)
16 {
17 private_data_ = new cmd_caller_private_data();
18 private_data_->cmd_str = std::string(cmdstr);
19 }
20
21 cmd_caller::cmd_caller(const std::string& cmdstr)
22 {
23 private_data_ = new cmd_caller_private_data();
24 private_data_->cmd_str = cmdstr;
25 }
26
27 bool cmd_caller::init_private_data()
28 {
29 private_data_->sa.nLength = sizeof(SECURITY_ATTRIBUTES);
30 private_data_->sa.lpSecurityDescriptor = NULL;
31 private_data_->sa.bInheritHandle = TRUE;
32
33 if (!CreatePipe(&private_data_->hPipeRead,
34 &private_data_->hPipeWrite,
35 &private_data_->sa,
36 0)) //創建管道)
37 {
38 return false;
39 }
40
41
42 private_data_->si.cb = sizeof(STARTUPINFOA);
43 GetStartupInfo(&private_data_->si); //設置啟動信息
44 private_data_->si.hStdError = private_data_->hPipeWrite; //將子進程錯誤輸出結果寫到管道
45 private_data_->si.hStdOutput = private_data_->hPipeWrite; //將子進程標準輸出結果寫出管道
46 private_data_->si.wShowWindow = SW_HIDE; //隱式調用子程序,及不顯示子程序窗口
47 private_data_->si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
48
49 return true;
50 }
51
52 cmd_caller::~cmd_caller()
53 {
54 delete private_data_;
55 }
56
57 void cmd_caller::set_cmd(const char* cmdstr)
58 {
59 private_data_->cmd_str = std::string(cmdstr);
60 }
61
62 void cmd_caller::set_cmd(const std::string& cmdstr)
63 {
64 private_data_->cmd_str = cmdstr;
65 }
66
67 bool cmd_caller::call_cmd(std::string& output)
68 {
69 if(!init_private_data())
70 {
71 return false;
72 }
73
74 //根據設定的參數創建并執行子進程
75 if(!CreateProcessA(NULL,
76 (LPSTR)private_data_->cmd_str.c_str(),
77 NULL,
78 NULL,
79 TRUE,
80 NULL,
81 NULL,
82 NULL,
83 &private_data_->si,
84 &private_data_->pi))
85 {
86 return false;
87 }
88
89 //等待子進程退出
90 if(WAIT_FAILED == WaitForSingleObject(private_data_->pi.hProcess, INFINITE))
91 {
92 return false;
93 }
94
95 //關閉子進程的相關句柄,釋放資源
96 CloseHandle(private_data_->pi.hProcess);
97 CloseHandle(private_data_->pi.hThread);
98
99 //Note:We must close the write handle of pipe, else the ReadFile call will pending infinitely.
100 CloseHandle(private_data_->hPipeWrite);
101
102 char buf[1024];
103 DWORD dwRead;
104 output.clear();
105
106 for (;;)
107 {
108 BOOL result = ReadFile(private_data_->hPipeRead, buf, 1024, &dwRead, NULL);
109
110 if(!result || dwRead == 0)
111 {
112 break;
113 }
114 else
115 {
116 output.append(buf, dwRead);
117 }
118 }
119 return true;
120 }
下面我們再來看一下Linux下的實現:
1 #include "cmd_caller.h"
2 #include <stdio.h>
3 #include <unistd.h>
4
5 struct cmd_caller_private_data
6 {
7 std::string cmd_str;
8 int pipefd[2];
9 };
10
11 cmd_caller::cmd_caller(const char *cmdstr)
12 {
13 private_data_ = new cmd_caller_private_data();
14 private_data_->cmd_str = std::string(cmdstr);
15 }
16
17 cmd_caller::cmd_caller(const std::string &cmdstr)
18 {
19 private_data_ = new cmd_caller_private_data();
20 private_data_->cmd_str = cmdstr;
21 }
22
23 cmd_caller::~cmd_caller()
24 { delete private_data_;
25 }
26
27 void cmd_caller::set_cmd(const char* cmdstr)
28 {
29 private_data_->cmd_str = std::string(cmdstr);
30 }
31
32 void cmd_caller::set_cmd(const std::string& cmdstr)
33 {
34 private_data_->cmd_str = cmdstr;
35 }
36
37 bool cmd_caller::init_private_data()
38 {
39 return (-1 != pipe(private_data_->pipefd));
40 }
41
42
43 bool cmd_caller::call_cmd(std::string &output)
44 {
45 if(!init_private_data())
46 return false;
47
48 int pid = fork();
49 if(-1 == pid)
50 {
51 return false;
52 }
53 else if(0 == pid)
54 {
55 //在子進程中,將管道的寫入文件描述符重定向到標準輸出和標準錯誤輸出
56 //這樣命令執行的結果將通過管道發送到父進程
57 dup2(private_data_->pipefd[1], STDOUT_FILENO);
58 dup2(private_data_->pipefd[1], STDERR_FILENO);
59
60 //在子進程中,我們不需要用到讀管道,把他關掉
61 close(private_data_->pipefd[0]);
62
63 if(-1 == execlp(private_data_->cmd_str.c_str(), private_data_->cmd_str.c_str(), NULL))
64 return false;
65
66 //寫管道已經用完,不需要在往管道中寫了,我們把它關了
67 close(private_data_->pipefd[1]);
68
69 return true;
70 }
71 else
72 {
73 char buf[1024];
74 size_t readsize;
75 //在父進程中我們不需要用到寫管道,可以關掉
76 close(private_data_->pipefd[1]);
77 while((readsize = read(private_data_->pipefd[0], buf, 1024)) > 0)
78 {
79 output.append(buf, readsize);
80 }
81 //讀管道已經用完,可以把它關掉
82 close(private_data_->pipefd[0]);
83 return true;
84 }
85 }
2 #include <stdio.h>
3 #include <unistd.h>
4
5 struct cmd_caller_private_data
6 {
7 std::string cmd_str;
8 int pipefd[2];
9 };
10
11 cmd_caller::cmd_caller(const char *cmdstr)
12 {
13 private_data_ = new cmd_caller_private_data();
14 private_data_->cmd_str = std::string(cmdstr);
15 }
16
17 cmd_caller::cmd_caller(const std::string &cmdstr)
18 {
19 private_data_ = new cmd_caller_private_data();
20 private_data_->cmd_str = cmdstr;
21 }
22
23 cmd_caller::~cmd_caller()
24 { delete private_data_;
25 }
26
27 void cmd_caller::set_cmd(const char* cmdstr)
28 {
29 private_data_->cmd_str = std::string(cmdstr);
30 }
31
32 void cmd_caller::set_cmd(const std::string& cmdstr)
33 {
34 private_data_->cmd_str = cmdstr;
35 }
36
37 bool cmd_caller::init_private_data()
38 {
39 return (-1 != pipe(private_data_->pipefd));
40 }
41
42
43 bool cmd_caller::call_cmd(std::string &output)
44 {
45 if(!init_private_data())
46 return false;
47
48 int pid = fork();
49 if(-1 == pid)
50 {
51 return false;
52 }
53 else if(0 == pid)
54 {
55 //在子進程中,將管道的寫入文件描述符重定向到標準輸出和標準錯誤輸出
56 //這樣命令執行的結果將通過管道發送到父進程
57 dup2(private_data_->pipefd[1], STDOUT_FILENO);
58 dup2(private_data_->pipefd[1], STDERR_FILENO);
59
60 //在子進程中,我們不需要用到讀管道,把他關掉
61 close(private_data_->pipefd[0]);
62
63 if(-1 == execlp(private_data_->cmd_str.c_str(), private_data_->cmd_str.c_str(), NULL))
64 return false;
65
66 //寫管道已經用完,不需要在往管道中寫了,我們把它關了
67 close(private_data_->pipefd[1]);
68
69 return true;
70 }
71 else
72 {
73 char buf[1024];
74 size_t readsize;
75 //在父進程中我們不需要用到寫管道,可以關掉
76 close(private_data_->pipefd[1]);
77 while((readsize = read(private_data_->pipefd[0], buf, 1024)) > 0)
78 {
79 output.append(buf, readsize);
80 }
81 //讀管道已經用完,可以把它關掉
82 close(private_data_->pipefd[0]);
83 return true;
84 }
85 }
到這里時,任務已近完成,然而,還有一種更簡單的調用方法,就是popen/pclose函數(Windows上是_popen/_pclose函數),下面就是利用這種方法的實現:
1 /*
2 *其他實現不變
3 */
4
5 bool cmd_caller::init_private_data()
6 {
7 return true;
8 }
9
10 bool cmd_caller::call_cmd(std::string &output)
11 {
12 char buffer[128];
13 FILE *fpipe;
14
15 if( (fpipe = popen(private_data_->cmd_str.c_str(), "r" )) == NULL )
16 return false;
17
18 //Read pipe until end of file, or an error occurs.
19
20 output.clear();
21 while(fgets(buffer, 128, fpipe))
22 {
23 output.append(buffer);
24 }
25
26 //Close pipe and print return value of fpipe.
27 if (feof(fpipe))
28 {
29 pclose(fpipe);
30 return true;
31 }
32 else
33 {
34 pclose(fpipe);
35 return false;
36 }
37 }
2 *其他實現不變
3 */
4
5 bool cmd_caller::init_private_data()
6 {
7 return true;
8 }
9
10 bool cmd_caller::call_cmd(std::string &output)
11 {
12 char buffer[128];
13 FILE *fpipe;
14
15 if( (fpipe = popen(private_data_->cmd_str.c_str(), "r" )) == NULL )
16 return false;
17
18 //Read pipe until end of file, or an error occurs.
19
20 output.clear();
21 while(fgets(buffer, 128, fpipe))
22 {
23 output.append(buffer);
24 }
25
26 //Close pipe and print return value of fpipe.
27 if (feof(fpipe))
28 {
29 pclose(fpipe);
30 return true;
31 }
32 else
33 {
34 pclose(fpipe);
35 return false;
36 }
37 }
同樣,在Windows上 :
1 /*
2 *其他實現不變
3 */
4
5 bool cmd_caller::init_private_data()
6 {
7 return true;
8 }
9
10 bool cmd_caller::call_cmd(std::string &output)
11 {
12 char psBuffer[128];
13 FILE *pPipe;
14
15 if( (pPipe = _popen(private_data_->cmd_str.c_str(), "rt" )) == NULL )
16 return false;
17
18 //Read pipe until end of file, or an error occurs.
19
20 output.clear();
21 while(fgets(psBuffer, 128, pPipe))
22 {
23 output.append(psBuffer);
24 }
25
26 //Close pipe and print return value of pPipe.
27 if (feof( pPipe))
28 {
29 _pclose(pPipe);
30 return true;
31 }
32 else
33 {
34 _pclose(pPipe);
35 return false;
36 }
37 }
38
2 *其他實現不變
3 */
4
5 bool cmd_caller::init_private_data()
6 {
7 return true;
8 }
9
10 bool cmd_caller::call_cmd(std::string &output)
11 {
12 char psBuffer[128];
13 FILE *pPipe;
14
15 if( (pPipe = _popen(private_data_->cmd_str.c_str(), "rt" )) == NULL )
16 return false;
17
18 //Read pipe until end of file, or an error occurs.
19
20 output.clear();
21 while(fgets(psBuffer, 128, pPipe))
22 {
23 output.append(psBuffer);
24 }
25
26 //Close pipe and print return value of pPipe.
27 if (feof( pPipe))
28 {
29 _pclose(pPipe);
30 return true;
31 }
32 else
33 {
34 _pclose(pPipe);
35 return false;
36 }
37 }
38
值得注意的是,以上兩種方法有下面一些不同:
1.第一種實現直接將子進程的標準輸出和錯誤輸出重定向到管道,而第二種實現卻需要在命令中指出重定向。
2.調用方式。如果命令是一個路徑,并且路徑中間有特殊字符如空格,那么兩種實現的調用方法可能有些區別,前一種方式更直接,而后一種方式需要加轉義。最后以Windows上的一個例子來說明這兩種調用之間的區別:
以第一種實現調用
cmd_caller caller("E:\\Program Files\\Nmap\\nmap.exe -T4 -A -v -PE -PS22,25,80 -PA21,23,80,3389 www.baidu.com")
對應于以第二種實現調用cmd_caller caller("\"E:\\Program Files\\Nmap\\nmap.exe\" -T4 -A -v -PE -PS22,25,80 -PA21,23,80,3389 www.baidu.com 2>&1")