HOWTO · C++

The system() Function in C++

This article introduces how to use a library function - system() in C++

On this page

This guide shows how to use the C++ standard library function system() to run a shell command, check whether a command processor is available, and interpret the result. It also explains why shell commands are a poor fit for untrusted input and when to use a process API instead.

What system() Does and When to Use It

std::system is declared in <cstdlib>. It passes a command string to the host environment’s command processor and waits synchronously for that processor to finish. The calling thread is blocked during this wait, so an interactive command such as top keeps the C++ program paused until the user exits the program.

This is a compact way to invoke a command when the command string is fixed and the program can wait. It is not the same as directly creating and managing a process: system() delegates command parsing to a shell and hides much of process creation and cleanup. That convenience adds overhead and makes behavior depend on the host’s command processor. On POSIX systems, lower-level choices include posix_spawn or the fork/exec family; Windows programs commonly use CreateProcess when they need direct process control.

Run a Shell Command with system()

The following example is intended for a POSIX-like environment with a shell that provides printf. The command writes one line, and system() waits for the shell command to finish before main returns. It uses a fixed command string, so no user input is interpreted as shell syntax.

Run this example:

#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;
}

The command runs in the process’s current working directory, so relative paths in the command are resolved from that directory. The shell and the command must exist in the environment, and command syntax differs across operating systems. For example, ls and top are common on Unix-like systems but are not portable C++ commands. The top example is interactive and intentionally waits for the user to exit; avoid placing it in an unattended program unless that behavior is wanted.

Check Shell Availability with system(nullptr)

Pass nullptr to ask whether a command processor is available. A nonzero result means one is available; zero means it is not. This check does not verify that a particular command exists, that its syntax is accepted, or that it will succeed. The result is specific to the host environment.

#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;
}

Use this as an environment check, not as a substitute for handling the result of the command you actually run. Availability can also change between checking and invoking a command.

Interpret the Return Value and Command Status

A return value of -1 indicates that the command processor could not be started or its status could not be obtained; on POSIX systems, errno provides additional error information. Any other result is the command processor’s termination status in the format used by the platform. It is not simply a Boolean success flag, nor is it necessarily the raw exit code of a command inside the shell.

On POSIX systems, include <sys/wait.h> and use WIFEXITED before calling WEXITSTATUS. The macros interpret the wait status returned by system(): WIFEXITED tells whether the shell exited normally, and WEXITSTATUS extracts its exit status only in that case. If the shell was terminated by a signal, WIFSIGNALED and WTERMSIG can identify that condition and signal. These macros are POSIX-specific; do not assume they are available or have the same meaning on other platforms.

This POSIX example asks the shell to exit with status 7. It prints the extracted status when the shell exits normally and handles other outcomes without treating the wait status as an exit code directly.

#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;
}

The shell’s status reflects how it handled the command string. Shell pipelines and compound commands have shell-defined status rules, so this value should not be described as the status of every process the shell may have started. For portable application logic, use the platform’s process API and inspect its documented result format.

Use system() Safely and Know Its Limits

Never concatenate untrusted text into a command passed to system(). Shell metacharacters, substitutions, and quoting rules can turn data into additional commands, creating command-injection vulnerabilities. Escaping arbitrary input correctly is difficult and varies by shell. A fixed command literal avoids that particular source of injection, but it does not make shell execution suitable for every application.

When arguments include user-controlled values, prefer an API that starts a program directly and passes arguments separately, such as posix_spawn on POSIX systems or CreateProcess on Windows with careful argument handling. Also consider whether the operation has a library API; avoiding a shell often improves portability, error handling, and control over process lifetime. Use system() only when invoking the host command processor is intentional, the command string is trusted, and waiting synchronously is acceptable.