HOWTO · C++
C++ 中的 system() 函式
本文介紹如何在 C++ 中使用一個庫函式——system()
本頁內容
本指南說明如何使用 C++ 標準函式庫的 system() 執行 Shell 命令、檢查命令處理器是否可用,以及判讀執行結果。本文也會說明為什麼不適合將不受信任的輸入交給 Shell,以及何時應改用程序 API。
system() 的功能與適用時機
std::system 宣告於 <cstdlib>。它會將命令字串傳給主機環境的命令處理器,並同步等待該處理器執行完畢。等待期間,呼叫端的執行緒會遭到阻塞;因此,像 top 這類互動式命令會讓 C++ 程式暫停,直到使用者結束該命令。
當命令字串固定,而且程式可以等待時,這是呼叫命令的簡便方式。但它並不等同於直接建立及管理程序:system() 會把命令解析交給 Shell,也隱藏了大部分程序建立與清理工作。這種便利性會增加額外負擔,而且行為取決於主機的命令處理器。在 POSIX 系統上,較底層的選擇包括 posix_spawn 或 fork/exec 函式系列;Windows 程式若需要直接控制程序,通常會使用 CreateProcess。
使用 system() 執行 Shell 命令
以下範例適用於類 POSIX 環境,且該環境的 Shell 必須提供 printf。命令會輸出一行文字;system() 會等待 Shell 命令執行完畢,之後 main 才會返回。範例使用固定的命令字串,因此不會將使用者輸入解讀為 Shell 語法。
請執行以下範例:
本節中的程式碼以及顯示的標籤或輸出仍為英文。
#include <cstdlib>
#include <iostream>
int main() {
const int status = std::system("printf 'Hello from the shell\n'");
if (status == -1) {
std::cout << "system() could not start the command processor\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
命令會在程序目前的工作目錄中執行,因此命令中的相對路徑會以該目錄為基準解析。環境中必須有 Shell 和指定命令,而且不同作業系統的命令語法可能不同。例如,ls 和 top 在類 Unix 系統上很常見,但它們不是可攜式的 C++ 命令。top 範例會進入互動模式,並刻意等待使用者結束;除非程式確實需要這種行為,否則不要在無人值守的程式中使用它。
使用 system(nullptr) 檢查 Shell 是否可用
傳入 nullptr,即可詢問命令處理器是否可用。結果非零表示可用;結果為零則表示不可用。這項檢查無法確認特定命令是否存在、語法是否會被接受,或命令能否成功執行。結果也僅適用於目前的主機環境。
本節中的程式碼以及顯示的標籤或輸出仍為英文。
#include <cstdlib>
#include <iostream>
int main() {
if (std::system(nullptr) != 0) {
std::cout << "A command processor is available.\n";
} else {
std::cout << "No command processor is available.\n";
}
return 0;
}
請將此檢查用於確認環境,而不要用它取代對實際命令執行結果的處理。此外,在檢查可用性之後、呼叫命令之前,環境狀態仍可能改變。
判讀回傳值與命令狀態
回傳值為 -1 表示命令處理器無法啟動,或無法取得其狀態;在 POSIX 系統上,可透過 errno 取得更多錯誤資訊。其他回傳值則是命令處理器依平台格式表示的終止狀態。它不只是表示成功或失敗的布林值,也不一定是 Shell 內某個命令的原始結束代碼。
在 POSIX 系統上,請包含 <sys/wait.h>,並在呼叫 WEXITSTATUS 前使用 WIFEXITED。這些巨集會解讀 system() 傳回的等待狀態:WIFEXITED 用來判斷 Shell 是否正常結束;只有在正常結束時,WEXITSTATUS 才會取出其結束狀態。如果 Shell 是因為訊號而終止,則可使用 WIFSIGNALED 和 WTERMSIG 判斷此情況及相關訊號。這些巨集僅適用於 POSIX;不要假設其他平台也提供這些巨集,或其意義相同。
此 POSIX 範例會要求 Shell 以狀態 7 結束。Shell 正常結束時,範例會輸出取出的狀態;若發生其他結果,也會妥善處理,而不會直接把等待狀態當作結束代碼。
本節中的程式碼以及顯示的標籤或輸出仍為英文。
#include <cstdlib>
#include <iostream>
#include <sys/wait.h>
int main() {
const int status = std::system("exit 7");
if (status == -1) {
std::cout << "Could not start the command processor.\n";
return EXIT_FAILURE;
}
if (WIFEXITED(status)) {
std::cout << "Shell exit status: " << WEXITSTATUS(status) << '\n';
} else if (WIFSIGNALED(status)) {
std::cout << "Shell ended from signal " << WTERMSIG(status) << '\n';
} else {
std::cout << "Shell ended in another state.\n";
}
return 0;
}
Shell 的狀態反映它如何處理命令字串。Shell 管線和複合命令的狀態規則由 Shell 定義,因此不應把這個值說成 Shell 可能啟動的每個程序的狀態。若應用程式邏輯需要可攜性,請使用平台提供的程序 API,並依其文件檢查結果格式。
安全使用 system() 及其限制
絕不要把不受信任的文字串接到傳給 system() 的命令中。Shell 中繼字元、替換語法和引號規則可能會讓資料變成額外命令,造成命令注入漏洞。要正確跳脫任意輸入並不容易,而且不同 Shell 的規則各異。使用固定的命令字面值可以避開這一類注入來源,但這不代表 Shell 執行適合所有應用程式。
如果引數包含由使用者控制的值,請優先使用可直接啟動程式並分別傳遞引數的 API,例如 POSIX 系統上的 posix_spawn,或 Windows 上妥善處理引數的 CreateProcess。也可以確認該操作是否有對應的函式庫 API;避免使用 Shell 通常能改善可攜性與錯誤處理,並讓程序生命週期更容易控制。只有在確實需要呼叫主機命令處理器、命令字串可信,而且可以接受同步等待時,才使用 system()。