C/C++ 取得 shell 指令的螢幕輸出字串

本篇介紹在 C/C++ 程式用 popen 來取得外部執行 shell command 指令結果(螢幕輸出字串),例如在程式裡執行 ls 指令,那我們怎麼透過 popen 獲得 ls 指令的輸出結果,請參考下面內容。

同樣是在程式裡執行 ls,來看看這幾個方式有什麼不同,
使用 system 來執行會等待system執行完(blocking),就接著往下執行了,
使用 popen 來執行不會等待執行完(non-blocking),才接著往下執行,
但是如果要讀取 popen 的執行結果就必須等待執行完成(blocking),才接著往下執行,
要記得這幾個之前差異,在使用上才能依情境挑選適合的 api 來使用。

範例. 用 popen 取得外部執行的結果

以下範例為執行一個外部指令 ls 並取讀取 ls 的輸出,將其輸出用 cout 印出來,

使用 popen 去建立一個管道 pipe,透過 fork 產生新的子行程,然後執行 ls 指令,
popen 回傳的是 FILE 類型,可以用讀取文件的方式去操作它,最後不使用時要 pclose,

其中 fgets 是每次讀取一行,也就是讀取到’\n’換行字元就會返回,
也可以換成 fread,視不同情形而定,
不斷地將讀取到的內容疊加在 result 裡最後返回,

cpp-get-exec-cmd-output.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// g++ cpp-get-exec-cmd-output.cpp -o a.out -std=c++11 -pthread
#include <iostream>
#include <stdexcept>
#include <stdio.h>
#include <string>

using namespace std;

bool shellCmd(const string &cmd, string &result) {
char buffer[512];
result = "";

// Open pipe to file
FILE* pipe = popen(cmd.c_str(), "r");
if (!pipe) {
return false;
}

// read till end of process:
while (!feof(pipe)) {
// use buffer to read and add to result
if (fgets(buffer, sizeof(buffer), pipe) != NULL)
result += buffer;
}

pclose(pipe);
return true;
}

int main() {
string result;
shellCmd("ls", result);
cout << result;

return 0;
}

有一定經驗的人都知道 popenpclose 只有在 unix 系統下有,在 windows 下是沒有 popenpclose,但是有對應的函式,
popenpclose 在 Windows 裡對應的函式為 _popen_pclose, 詳情請看微軟官方文件

使用 C++11 的寫法

cpp-get-exec-cmd-output-cpp11.cpp
1
TBD

進階. 寫個像 tail 或 tee 指令的程式

tail -f指令可以持續監控對某個檔案,該檔案一旦有新增便會將內容印出來,
tee 指令是將某指令的標準輸出重新導到檔案也順便導到標準輸出,

其訣竅在持續讀取,

cpp-get-exec-cmd-output2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// g++ cpp-get-exec-cmd-output2.cpp -o a.out -std=c++11 -pthread
#include <iostream>
#include <stdexcept>
#include <stdio.h>
#include <string>
#include <thread>

using namespace std;

bool stop = false;

bool shellCmd(const string &cmd) {
char buffer[512];

// Open pipe to file
FILE* pipe = popen(cmd.c_str(), "r");
if (!pipe) {
return false;
}

// read till end of process:
while (!feof(pipe) || stop) {
// use buffer to read and add to result
if (fgets(buffer, sizeof(buffer), pipe) != NULL)
cout << buffer;
}

pclose(pipe);
return true;
}

int main() {
thread th1 = thread(shellCmd, "tail -f /var/log/syslog");

std::this_thread::sleep_for(std::chrono::seconds(10));
stop = true;
th1.join();

return 0;
}

不過在結束時可能會卡在 plcose 結束不了有點小缺陷,之後有時間再把它改寫吧~

參考
[1] process - How do I execute a command and get the output of the command within C++ using POSIX? - Stack Overflow
https://stackoverflow.com/questions/478898/how-do-i-execute-a-command-and-get-the-output-of-the-command-within-c-using-po
[2] How to execute a command and get the output of command within C++ using POSIX?
https://www.tutorialspoint.com/How-to-execute-a-command-and-get-the-output-of-command-within-Cplusplus-using-POSIX
有效
[3] c++11 - How to execute a command and get return code stdout and stderr of command in C++ - Stack Overflow
https://stackoverflow.com/questions/52164723/how-to-execute-a-command-and-get-return-code-stdout-and-stderr-of-command-in-c

其它相關文章推薦
C/C++ 新手入門教學懶人包
C++ std::sort 排序用法與範例完整介紹
std::queue 用法與範例
std::thread 用法與範例
C++ virtual 的兩種用法
C/C++ 判斷檔案是否存在
C++ 設計模式 - 單例模式 Singleton Pattern
C/C++ call by value傳值, call by pointer傳址, call by reference傳參考 的差別