Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: December 30, 2024
Interfacing with the command line from a C++ program in Linux can open up numerous possibilities, such as automating tasks or interacting with system utilities.
In this tutorial, we’ll explore how to execute shell commands from C++ in Linux.
To begin with, we’ll cover the system() function for executing Linux commands. After that, we’ll discuss the gnome-terminal command and two ways to use it. Subsequently, we’ll look at the exec family to call Linux commands from a C++ program. Lastly, we’ll discuss a pure C++ approach that utilizes boost.process to execute the command line code.
The system() function from the <cstdlib> library is perhaps the simplest way to execute shell commands in C++. To elaborate, it spawns a new shell process, executes the supplied command, and returns control to the C++ program.
The system() function accepts a string containing a shell command, executes it, and returns control to the calling C++ program once the command finishes. It uses the default system shell to run the command. For Unix-based systems, it commonly uses /bin/sh.
For example, to use the system() function to list files in the current directory, we can supply the ls command:
$ cat system.cpp
#include <cstdlib>
#include <iostream>
int main() {
std::cout << "Listing files:\n";
system("ls");
std::cout << "\nCreating a new directory 'testdir'\n";
system("mkdir testdir");
std::cout << "\nListing files after creating 'testdir':\n";
system("ls");
return 0;
}
To execute the program, we compile it using the gcc compiler, and execute the resulting binary:
$ gcc system.cpp system
$ ./system
The output first lists the files. After that, the program creates a new directory called testdir, and lists the files again:
Listing files:
file1.txt file2.cpp docs
Creating a new directory 'testdir'
Listing files after creating 'testdir':
file1.txt file2.cpp docs testdir
Thus, we employ shell commands from within the C++ program.
This method is usually the easiest to implement and doesn’t require extensive error handling. In addition, the system() function is available across different platforms, making it useful in programs that need to interact with the shell on both Unix-based systems and Windows.
Despite its simplicity, system() has some downsides.
Firstly, each call to system() spawns a new shell process. Any changes made, like changing the current directory with cd, don’t persist after the command finishes. If we need to perform multiple commands in the same shell environment, we must chain them together in a single string or set up the environment anew each time.
Furthermore, if user input is passed directly to the system(), it can expose the program to command injection attacks. For example, if we allow users to input a filename or command, that malicious input could execute arbitrary commands.
Alternatively, we might want to open a new terminal window and execute specific commands. For this purpose, we use the gnome-terminal command.
The general syntax for executing commands via gnome-terminal consists of consecutive commands:
$ gnome-terminal -x sh -c 'command1; command2; exec bash'
In this approach, we replace command1 and command2 with the specific commands we wish to run. This solution keeps the terminal open after executing the commands by using exec bash.
One approach to opening a new terminal and executing commands is using the gnome-terminal command with the — bash -c option. This enables us to pass a string of commands that the terminal should run when it starts.
For instance, to open a new terminal and execute code from a C++ program, we use the gnome-terminal command within a system() call:
$ cat gnome.cpp
#include <cstdlib>
#include <iostream>
int main() {
system("gnome-terminal -- bash -c \"cd ~ && ls; exec bash\"");
return 0;
}
The output consists of the commands that we executed:
total 0
drwxrwxrwt 27 root wheel 864 24 Dec 15:27 .
drwxr-xr-x 6 root wheel 192 19 Dec 09:40 ..
-rw-r--r-- 1 sidrah wheel 0 20 Dec 12:46 .fctshutdown.lck
…
Let’s look at the code explanation:
The exec bash command is essential to keep the terminal window open after the initial commands finish executing. Without it, the terminal window would immediately close. To conclude, this method is useful when we need to execute multiple commands in a specific order and then leave the terminal open for further use or interaction.
Another method to execute command line code from a C++ program is to use the -x sh -c option of gnome-terminal. This option opens a new terminal and executes a specified command. Notably, this approach is slightly more concise as compared to the –bash approach.
Let’s modify the same example gnome.cpp to use -x sh -c:
$ cat gnome.cpp
#include <cstdlib>
#include <iostream>
int main() {
system("gnome-terminal -x sh -c 'cd /tmp ; ls -la ; exec bash'");
return 0;
}
In the above code, we tell gnome-terminal to start a new shell session using sh -c and execute the command passed to it. After that, the cd /tmp ; ls -la commands are executed sequentially. cd /tmp changes the directory to /tmp, while ls -la lists the files in the /tmp directory in long format with detailed information.
This solution is particularly useful for simple tasks where complex command chaining isn’t required. With both approaches, we can pass additional options to customize the new terminal window. In addition, if any commands fail, the terminal shows an error message. This can be helpful for debugging scripts or automating tasks.
Another approach to executing Linux commands in a C++ program uses the exec family of functions. These functions replace the current process image with a new one and execute the specified command.
There are different types of exec functions:
Notably, Unlike system(), these functions do not spawn a new shell.
For example, to execute the ls command from a C++ program, we use the excel command:
$ cat execExample.cpp
#include <unistd.h>
#include <iostream>
int main() {
std::cout << "Executing the 'ls' command:\n";
execlp("ls", "ls", "-l", NULL);
perror("execlp failed");
return 1;
}
In the above code, unistd.h is a header file provided by POSIX-compliant operating systems and not part of the C or C++ standard libraries. It provides various system-level functions, such as file operations, process control, and other OS-specific features. Moreover, the first parameter in execlp specifies the command to be executed. Subsequently, the other parameters are arguments for the command. In addition, the last parameter must be NULL to terminate the argument list. If the command executes successfully, the process image is replaced, and the code after execlp doesn’t execute.
The exec functions offer more control than the system() function. Notably, these functions avoid spawning a new shell, which reduces overhead. They are also safer if user input is validated and arguments are properly sanitized.
However, exec functions do have some limitations. First, they replace the current process, so it cannot return to the original program after execution. Additionally, error handling is more complex since the command replaces the process.
Notably, the exec family of functions is suitable for scenarios where a new shell isn’t required. Furthermore, we use it when we need greater efficiency or control over the execution. Hence, this method provides an efficient and secure alternative to system(), especially for executing a single command or script without needing a full shell environment.
Another modern C++ approach to execute Linux commands is boost.process. Unlike the exec*() functions, which depend on OS-specific headers like unistd.h, boost.process is a pure C++ solution, ensuring portability and better error handling.
To start using boost.process, we first install the boost library:
$ sudo apt-get install libboost-all-dev
Now, to execute the ls command from the C++ program, we include the relative libraries and execute the boost.process command:
$ cat main.cpp
#include <boost/process.hpp>
#include <iostream>
int main() {
try {
boost::process::system("ls");
} catch (const boost::process::process_error& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
In the above code, we use boost::process::system to execute the terminal commands. In addition, this enables us to capture the output of the executed commands. Furthermore, we can pass arguments to the commands too.
Notably, when compiling the program, we tell the compiler where to find the boost header files:
$ g++ -o my_program main.cpp -I/usr/local/include -L/usr/local/lib -lboost_system -lboost_filesystem -lboost_process
Let’s understand this command:
Once we execute the program, we can see the command output:
$ ./my_program
Applications Documents Library Pictures
Downloads Movies Public
Desktop Google Drive Music
Thus, boost.process is platform-independent and doesn’t rely on system-specific headers, making it suitable for cross-platform development.
In this article, we covered how to execute command line code from C++ in Linux.
To begin with, we covered the system() command. Moreover, we also discussed the gnome-terminal and exec family system calls. Lastly, we covered the boost.process approach.