BashSpark is a powerful C++ library that provides a command-line tool to execute custom commands seamlessly.
Key Features
- Bash Inspiration: While BashSpark draws inspiration from the Bash shell, it introduces unique features and behaviors tailored for enhanced usability.
- Custom Command Execution: Users can easily define and execute their own commands, allowing for flexible scripting and automation.
- Rich Functionality: The library supports various command types and execution options, accommodating diverse user needs.
BashSpark caters to developers seeking a robust and flexible tool for command execution in C++. It's familiar syntax makes it easier for the user to get started with it.
Key Features:
- Supports Bash-style syntax:
${VARIABLE_NAME}${!VARIABLE_NAME}$(command)function function_name {...}- Does not recognize semicolons/endls generated by the expansion.
Expression
$cmdwherecmd="echo -n 1; echo -n 2"will output1; echo -n 2, not12.
Unique Capabilities:
- Interprets escaped characters, such as
\n(newline) and\t(tab). - Supports Unicode code points with the following formats:
\xfor ascii\ufor UTF-16\Ufor UTF-32
- Returns
shell_status::SHELL_ERROR_BAD_ENCODINGfor invalid code points.
Different behaviour
- Does not require space around '(', '[', '{', '}', ']' and ')'.
- Characters '(', '[', '{', '}', ']' and ')' are always interpreted.
- Example: "echo }" works in bash, but not in BashSpark, although "echo \}" works in both.
- Example: "((echo d))" does not work in bash, but works in BashSpark (since they are always interpreted, the spaces are not necessary)
- Supports empty commands. Syntax like ";;;" is valid (three times empty, whom returns 0).
- Does not support improved test "[[...]]"
- Normal test supports operators "||" and "&&"
- Normal test supports parentheses "(...)"
- Does not support arithmetic with (()), the user may use command
math - Does not support syntax:
variable=value, use commandssetenvandsetvar. - Does not support syntax:
function_name(){...} - Does not support syntax:
function_name <args>, use commandfcall function_name <args>
Command management
Commands are instantiations of class bs::command that are added to the shell.
This command management method allow the programmer to strictly control the allowed
behaviors, preventing security holes. This enhances the safety and reliability
of command execution, ensuring that only authorized commands are executed
and that errors are handled gracefully.
** About the shell depth **
The shell parser has a limit to the parsing depth. This is to prevent the program to run
out of stack. The user is free to modify the constant std::size_t bs::shell::MAX_DEPTH
and rebuild the project with the appropriate compiler flags if needed. The default value
of this constant is 16, which was determined enough for general purpose scripts.
Increases the depth:
- Parsing subblocks like "(...)" and "{...}"
- Parsing subcommands like "$(...)"
- Command eval (starts a brand-new shell with new depth in parsing).
- Keywords like "if", "elif", "for", "while", "until". It increases the session shell depth. Custom commands using subshells should increase the session shell depth.
Does not increase the depth:
- Parsing variables like "${...}"
- Parsing tests like "[...]"
- Keywords like "then", "fi", "else", "do", "done"
** Known Issues **
The implementation, as is, is ATTEMPTS to prevent SIGSEGV. The shell execution does not throw
any exceptions except std::bad_alloc or exceptions derived from the streams provided
by the programmer. All errors that may occur by default are listed on bs::shell_status
and the shell will notify of them throw stderr + error code of last command. This includes
shell syntax errors and shell depth limit errors.
Other issues:
- Shell arguments are limited up to 19 digits, limited by the std::uint64_t range.
This file is part of BashSpark.
Copyright (C) 2025 Dante Doménech Martínez
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/
To use this project in your own, the simplest method is to integrate it via CMake.
Add the following lines to your CMakeLists.txt file:
# Include FetchContent
include(FetchContent)
# Fetch the repository
FetchContent_Declare(
bashspark
GIT_REPOSITORY https://github.com/dante19031999/bashspark.git
GIT_TAG master
)
FetchContent_MakeAvailable(bashspark)
# Define your library
add_library(foo ...)
# Link your library with BashSpark
target_link_libraries(foo PRIVATE BashSpark::BashSpark)You can choose from several aliases depending on your needs:
BashSpark::ABashSpark: static library.BashSpark::FBashSpark: static library of the project with position-independent code.BashSpark::SBashSpark: dynamic library.BashSpark::BashSparkdefault library (dynamic library).
This project includes comprehensive documentation detailing its functionality and operations.
To generate the documentation, follow these steps:
- The CMake configuration file includes a dedicated "doc" target.
- Executing this target will generate the Doxygen documentation in
${PROJECT_DIR}/docs. - The primary access point for the documentation is the HTML file located at
${PROJECT_DIR}/docs/html/index.html.
The library Doxygen Awesome is used in order to improve the documentation aesthetics.
The cmake file contains various predefined targets to build the project
doc: Generates documentation (see Generating the documentation).deb: Generate.debfile of the library at${PROJECT_DIR}/build/bashspark_1.0_amd64.deb.rpm: Generate.rmpfile of the library at${PROJECT_DIR}/build/bashspark_1.0_amd64.rpm(for the time being unavailable).headersGenerates a zip file containing the library headers at${PROJECT_DIR}/build/BashSpark.zip.abashspark: Generates a static library of the project.fbashspark: Generates a static library of the project with position-independent code.sbashspark: Generates a dynamic library of the project.tbashspark: Generates the test executable of the project.
Exposes targets:
BashSpark::ABashSpark: static library.BashSpark::FBashSpark: static library of the project with position-independent code.BashSpark::SBashSpark: dynamic library.BashSpark::BashSparkdefault library (dynamic library).- Property
BASHPARK_HEADER_DIRwith the include path.
Note, if command debuild is missing, use:
sudo apt install devscriptsInstall cmake:
| Linux Distribution | Command |
|---|---|
| Debian-based | sudo apt install -y cmake |
| Red Hat-based | sudo yum install -y cmake |
| Fedora | sudo dnf install -y cmake |
| Arch Linux | sudo pacman -S --noconfirm cmake |
| Windows | https://cmake.org/download/ |
| MAC OS X | brew install cmake |
The installation is separated in three steps:
- Configure cmake
- Unpacking the headers
- Building the library
- Copying the library
First is to set the project directory. This simplifies the execution of the further commands.
Assuming that the bash working directory is in the project directory it would be:
BASH_SPARK="$(realpath .)"The following step is to configure cmake. This processes the CMakeLists.txt and prepares cmake for the build.
It is achieved with the following command:
mkdir -p ${BASH_SPARK}/cmake-build && cd ${BASH_SPARK}/cmake-build && cmake ..On posix system the library headers are stored at /usr/include. Therefore, the headers need to be copied there.
Generate the headers:
cd ${BASH_SPARK}/cmake-build && make headersExtract the headers:
tar -xzf ${BASH_SPARK}/build/BashSpark.tar.gz -C ${BASH_SPARK}/build --strip-components=1 "./BashSpark"Move headers to /usr/include:
sudo mv ${BASH_SPARK}/build/BashSpark /usr/include/BashSparkWindows users:
On windows extract the file ${BASH_SPARK}/build/BashSpark.tar.gz where appropriate.
Choose a version of the library to build. If unsure choose sbashspark.
abashspark: Generates a static library of the project.fbashspark: Generates a static library of the project with position-independent code.sbashspark: Generates a dynamic library of the project.
Respective commands:
Library abashspark:
cd ${BASH_SPARK}/cmake-build && make abashsparkLibrary fbashspark:
cd ${BASH_SPARK}/cmake-build && make fbashsparkLibrary sbashspark:
cd ${BASH_SPARK}/cmake-build && make sbashsparkOn posix system the library headers are stored at /usr/lib. Therefore, the headers need to be copied there.
Create the directory for the library:
sudo mkdir -p /usr/lib/BashSparkMove the libraries:
Library abashspark:
sudo mv ${BASH_SPARK}/build/libabashspark.a /usr/lib/BashSpark/libabashspark.aLibrary fbashspark:
sudo mv ${BASH_SPARK}/build/libfbashspark.a /usr/lib/BashSpark/libfbashspark.aLibrary sbashspark:
sudo mv ${BASH_SPARK}/build/libsbashspark.so /usr/lib/BashSpark/libsbashspark.soThis section explains the syntax and usage of some special shell commands.
The echo command writes its parameters to stdout exactly as provided.
If the -n option is used as the first parameter, no newline is printed at the end of the output.
Examples:
| Command | Output |
|---|---|
echo "Hello World!" |
Hello World!\n |
echo -n "Hello World!" |
Hello World! |
The getenv command retrieves the value of an environment variable.
If the variable does not exist, it returns an empty string "".
Requires one argument: a valid environment variable name (like those in Bash).
| Error Code | Meaning |
|---|---|
bs::shell_status::SHELL_CMD_ERROR_GETENV_PARAM_NUMBER |
Wrong number of parameters provided |
bs::shell_status::SHELL_CMD_ERROR_GETENV_VARIABLE_NAME_INVALID |
Provided variable name is invalid |
Example: getenv USER
The setenv command sets the value of an environment variable or creates it if it does not exist.
Requires two arguments: a valid environment variable name (like those in Bash) and a value.
| Error Code | Meaning |
|---|---|
bs::shell_status::SHELL_CMD_ERROR_SETENV_PARAM_NUMBER |
Wrong number of parameters provided |
bs::shell_status::SHELL_CMD_ERROR_SETENV_VARIABLE_NAME_INVALID |
Provided variable name is invalid |
Example:
setenv SHELL "BashSpark"
The getvar command retrieves the value of a local variable.
If the variable does not exist, it returns an empty string "".
Requires one argument: a valid variable name (like those in Bash).
| Error Code | Meaning |
|---|---|
bs::shell_status::SHELL_CMD_ERROR_GETVAR_PARAM_NUMBER |
Wrong number of parameters provided |
bs::shell_status::SHELL_CMD_ERROR_GETVAR_VARIABLE_NAME_INVALID |
Provided variable name is invalid |
Example:
getvar variable
The setvar command sets the value of a local variable or creates it if it does not exist.
Requires two arguments: a valid variable name (like those in Bash) and a value.
| Error Code | Meaning |
|---|---|
bs::shell_status::SHELL_CMD_ERROR_SETVAR_PARAM_NUMBER |
Wrong number of parameters provided |
bs::shell_status::SHELL_CMD_ERROR_SETVAR_VARIABLE_NAME_INVALID |
Provided variable name is invalid |
Example: setvar variable "value"
The seq command creates a sequence of integers.
The sequence can be growing or shrinking.
Takes 3 integer parameters: start, step, end.
- The
stepparameter is optional and defaults to1or-1depending on the sequence direction. - The sequence must extend over a closed set of values (logic error if violated).
- Integer parameters are limited to 18 digits (64-bit signed integers).
| Error Code | Meaning |
|---|---|
bs::shell_status::SHELL_CMD_ERROR_SEQ_PARAM_NUMBER |
Wrong number of parameters |
bs::shell_status::SHELL_CMD_ERROR_SEQ_INVALID_INT_FORMAT |
Parameter is not a valid integer |
bs::shell_status::SHELL_CMD_ERROR_SEQ_INT_OUT_OF_BOUNDS |
Parameter exceeds 64-bit integer limits |
bs::shell_status::SHELL_CMD_ERROR_SEQ_ITERATION_LOGIC |
Step and range do not allow a valid sequence |
Examples:
| Command | Output |
|---|---|
seq 1 5 |
1 2 3 4 5 |
seq 1 2 5 |
1 3 5 |
seq 5 -2 1 |
5 3 1 |
seq 5 1 |
5 4 3 2 1 |
echo -n $(seq 1 5) |
1 2 3 4 5 |
The math command performs simple integer arithmetic.
It takes mathematical expressions as parameters, broken down into independent tokens.
Supports:
- Operators:
+,-,*,/,^,**,×,÷ - Integer numbers within the range of 64-bit signed integers
- Parentheses:
()(including nested parentheses) - Negative powers partially:
{ x != 0 } ^ { y < 0 } = 0 - Functions:
factorial(x)sum(variable; start; end; step; expression)— sumsexpressionover the sequence ofvariable, e.g.,count = sum(x;1;1;100;1)product(variable; start; end; step; expression)— multipliesexpressionover the sequence ofvariable, e.g.,factorial = product(x;1;1;100;x)- Note: a variable inside
sum/producthides the same variable from the outside scope - Variable names must be valid shell variable names
Possible errors:
| Error Code | Meaning |
|---|---|
bs::shell_status::SHELL_CMD_ERROR_MATH_NOT_AN_INTEGER |
Value is not an integer |
bs::shell_status::SHELL_CMD_ERROR_MATH_OVERFLOW |
Integer overflow |
bs::shell_status::SHELL_CMD_ERROR_MATH_UNDERFLOW |
Integer underflow |
bs::shell_status::SHELL_CMD_ERROR_MATH_DIV_BY_ZERO |
Division by zero |
bs::shell_status::SHELL_CMD_ERROR_MATH_POW_0_EXP_0 |
0^0 power is undefined |
bs::shell_status::SHELL_CMD_ERROR_MATH_FACTORIAL_NEGATIVE |
Factorial of negative number |
bs::shell_status::SHELL_CMD_ERROR_MATH_MALFORMED_EXPRESSION |
Expression is malformed |
bs::shell_status::SHELL_CMD_ERROR_MATH_MAX_DEPTH_REACHED |
Maximum recursion depth reached (512) |
bs::shell_status::SHELL_CMD_ERROR_MATH_INVALID_VARIABLE_NAME |
Invalid variable name in function |
bs::shell_status::SHELL_CMD_ERROR_MATH_SEQ_ITERATION_LOGIC |
Sequence logic error in sequence function |
Examples:
| Command | Output |
|---|---|
math + 1 |
1 |
math - 1 |
-1 |
math +1 |
1 |
math -1 |
-1 |
math 3 + 4 |
7 |
math 3 * 4 |
12 |
math 12 / 4 |
3 |
math 12 % 5 |
2 |
math 2 ^ 3 |
8 |
math 2 ** 3 |
8 |
math 2 ** - 3 |
0 |
math 2 + 2 * 2 + 2 ^ 2 + 2 * 2 + 2 |
16 |
math 42 ^ 0 + 1 ^ 42 + 0 ^ 42 |
2 |
math \( 2 + 2 \) * \( 2 + 2 \) ^ \( 2 + 2 \) * \( 2 + 2 \) |
4096 |
math $(echo "( 2 + 2 ) * ( 2 + 2 ) ^ ( 2 + 2 ) * ( 2 + 2 )") |
4096 |
math \( \( 2 + 2 \) * \( 2 + 2 \) \) ^ \( \( 1 + 2 \) * \( 1 + 2 \) \) |
68719476736 |
math factorial \( 5 \) |
120 |
math product \( x , 1 , 1 , 5 , x \) |
120 |
math sum \( x , 1 , 1 , 5 , x \) |
15 |
echo $( math sign \( - 42 \) ) $( math sign \( 0 \) ) $( math sign \( + 42 \) ) |
-1 0 1 |
math abs \( - 42 \) |
42 |
math abs \( + 42 \) |
42 |
setvar i $(math i + 1) |
`` |
The test command performs simple tests.
It takes test expressions as parameters, broken down into independent tokens.
Capabilities:
- Operators or: -o, ||
- Operators and: -a, &&
- Comparison operators: ==, -eq, >, -gt, <, -lt, >=, -ge, <=, -le Note that comparison operators will apply numeric comparison if both arguments are numbers or <=> on std::string if they aren't.
- Operator =
: use a =b, checks if a matches b. Uses C++ regex, not bash regex. - Parentheses: ( ) (and nested parentheses).
Possible status codes outputs:
| Status | Description |
|---|---|
bs::shell_status::SHELL_SUCCESS |
Test passed |
bs::shell_status::SHELL_CMD_TEST_FALSE |
Test failed |
bs::shell_status::SHELL_CMD_ERROR_TEST_UNCLOSED_PARENTHESIS |
Opened parenthesis not closed |
bs::shell_status::SHELL_CMD_ERROR_TEST_MALFORMED_EXPRESSION |
Malformed expression |
bs::shell_status::SHELL_CMD_ERROR_TEST_MALFORMED_REGEX |
Malformed regex |
Examples:
| Command | Meaning | Result |
|---|---|---|
test -z "" |
Is the string empty? | true |
test -n "d" |
Is the string non-empty? | true |
test 7 -eq 0007 |
Numeric equality (leading zeros allowed) | true |
test 7 != 42 |
Numeric inequality | true |
test abc != 42 |
String inequality | true |
test 7 -gt 6 |
Numeric greater-than | true |
test b > a |
Lexicographic greater-than | true |
test 6 -le 7 |
Numeric ≤ comparison | true |
test 'hello' =~ '^h.*o$' |
Regex match | true |
test 'abc' =~ '^[0-9]+$' |
Regex check: digits only? | false |
test 'test@example.com' =~ '^[^@]+@[^@]+\\.[^@]+$' |
Email-like pattern | true |
The fcall command calls functions.
The first parameter is the function name.
The other parameters are the function parameters.
Example:
function echon {
echo -n $@
}
fcall echon Hello World!Output: Hello World!
This section displays some simple example scripts. Allows to better grasp the shell syntax.
Hello world:
echo -n 'Hello, World!'"Output: Hello, World!\n
While loop:
setvar count 1
while [ $count <= 5 ];
do
echo \"Count: $count\"; setvar count $(math $count + 1);
doneOutput:
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
Until loop:
setvar count 1
until [ $count > 5 ];
do
echo \"Count: $count\"; setvar count $(math $count + 1);
doneOutput:
Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
Loop for:
for i in $(seq 1 5);
do
echo -n $i;
doneOutput: 12345
Function:
function greet {
echo \"Ave $1\"
}
fcall greet CesarOutput: Ave Cesar\n
Count function arguments:
function count_args {
echo -n $#
}
fcall count_args $(seq 1 5)Output: 5
Function with if-else block:
function oddeven {
if [ $(math $1 % 2) == 0 ];
then
echo \"$1 is even\"
else
echo \"$1 is odd\"
fi
}
fcall oddevenOutput:
42 is even
11 is odd
Function that displays it's arguments:
function show_args {
if [ $# > 1 ];
then
for i in $(seq 1 $#);
do
echo \"arg $i: \\u201C${!i}\\u201D\";
done
else
echo 'No arguments';
fi
}
fcall show_args
fcall show_args $(seq 1 5)Output:
No arguments
arg 1: “1”
arg 2: “2”
arg 3: “3”
arg 4: “4”
arg 5: “5”
This section provides detailed instructions on how to utilize the BashSpark library effectively. It covers essential operations such as creating a shell instance, initializing a shell session, executing commands, and developing custom commands.
By following these guidelines, developers can manage command execution securely and efficiently while avoiding common pitfalls. The examples included showcase practical implementations, helping users to grasp each concept through hands-on coding snippets. Whether you are new to the library or seeking to enhance your command structures, this section serves as a comprehensive guide for operational success.
The first step to run commands is to create a shell. This class is named bs::shell.
This class manages the available commands.
This command management method allow the programmer to strictly control the allowed behaviors, preventing security holes. This enhances the safety and reliability of command execution, ensuring that only authorized commands are executed and that errors are handled gracefully.
It is possible to obtain the default shell using method std::unique_ptr<bs::shell> bs::shell::make_default_shell().
The
user may also use the default constructor bs::shell() to create a custom shell.
The class shell session may have the following methods overridden to change behavior (such as internationalization):
void shell::msg_error_command_not_found(shell_session &, const std::string &) const: error message on command not found.void shell::msg_error_syntax_error(const shell_session &, const shell_exception &) const: error message on syntax error.
Example on obtaining the default session:
std::unique_ptr<bs::shell> pShell = bs::shell::make_default_shell();Example on building the default session:
std::unique_ptr<shell> shell::make_default_shell() {
auto pShell = std::make_unique<shell>();
pShell->set_command<command_echo>();
pShell->set_command<command_eval>();
pShell->set_command<command_getenv>();
pShell->set_command<command_getvar>();
pShell->set_command<command_setenv>();
pShell->set_command<command_setvar>();
pShell->set_command<command_seq>();
pShell->set_command<command_test>();
pShell->set_command<command_math>();
pShell->set_command<command_fcall>();
return pShell;
}In order to execute shell commands a shell session is needed. This class is named bs::shell_session.
The class bs::shell_session contains the streams (stdin, stdout, stderr), the environment variables, the function
arguments, the local variables, the last exit status code and the shell depth.
See the documentation on bs::shell_session for more details.
A bs::shell_session is constructed as
shell_session(const shell *pShell, std::istream &oStdIn, std::ostream &oStdOut, std::ostream &oStdErr).
The class bs::shell_session may be extended in order to add custom information to the session.
If a custom command uses the custom session, on method
bs::command::run(const std::span<const std::string> &, shell_session &) const,
it will be necessary to use auto pCustomSession = dynamic_cast<shell_session_custom*>(&oSession) in order to acquire
it.
Example of shell session directly linked to terminal:
// Generate shell
std::unique_ptr<bs::shell> pShell = bs::shell::make_default_shell();
// Instantiare session
bs::shell_session oSession(
pShell.get(), // Owning shell
std::cin, // stdin
std::cout, // stdout
std::cerr // stderr
);Running a command requires instantiating the sell session and using the methods
shell_status shell::run(std::istream &oCommand,shell_session &oSession);,
shell_status run(const std::string &sCommand, shell_session &oSession); or
shell_status run(const std::string_view &sCommand, shell_session &oSession);.
Example of hello world:
bs::shell_status run_helloworld() {
// Generate shell
std::unique_ptr<bs::shell> pShell = bs::shell::make_default_shell();
// Instantiare session
bs::shell_session oSession(
pShell.get(), // Owning shell
std::cin, // stdin
std::cout, // stdout
std::cerr // stderr
);
// Run command
return bs::shell::run("echo -n 'Hello world!'"sv, oSession);
}Creating a custom command requires implementing the bs::command interface.
The custom command class has to override the method
bs::command::run(const std::span<const std::string> &, shell_session &) const.
On success, the run method must return bs::shell_status::SHELL_SUCCESS.
On failure, the run method must return return bs::make_user_code(USER_ERROR_CODE) where USER_ERROR_CODE is an
unsigned int.
It is recommended to define supplementary functions to print the error messages.
Such functions would have a prototype like: print_error(std::ostream& oStderr) const.
Use the function bs::shell::set_command(std::unique_ptr<command> &&) to add the custom commands.
The helper template template<typename CommandT, typename... Args> void bs::shell::set_command(Args &&...) may also be
useful.
If the custom command uses a custom session, on method
bs::command::run(const std::span<const std::string> &, shell_session &) const,
it will be necessary to use auto pCustomSession = dynamic_cast<shell_session_custom*>(&oSession) in order to acquire
it.
If the custom command calls subshells, to prevent SIGSEGV by stack overflow it is recommended to check the shell depth. Custom commands should not modify this depth lightly.
Example:
/**
* @class command_helloworld
* @brief Prints "Hellow world!" on stdout.
* Syntax: helloworld
*/
class command_helloworld : public bs::command {
public:
/**
* @brief Constructs the command
*
* Instantiates bs::command with the command name
*/
command_helloworld()
: bs::command("helloworld") {
}
public:
/**
* @brief Prints "Hellow world!" on stdout
*
* If the argument number is different from 0 then msg_error_param_number is called.
*
* @param vArgs Command arguments
* @param oSession Shell session
* @return Status code
*/
shell_status run(const std::span<const std::string> &vArgs, shell_session &oSession) const override {
// Check there are 0 arguments
if (!vArgs.empty()) {
// Call error printing functon
this->msg_error_param_number(
oSession.err(), // stderr
vArgs.size() // Argument number
);
// Return error code 1 (make user code)
return bs::make_user_code(1);
}
// Print "Hello World! on stdout
oSession.out() << "Hello World!";
// Return sucess code
return bs::shell_status::SHELL_SUCCESS;
}
/**
* @brief Print an error if the wrong number of arguments is provided.
* @param oStdErr Stream to print error message.
* @param nArgs Number of provided arguments.
*/
virtual void msg_error_param_number(std::ostream &oStdErr, const std::size_t nArgs) const {
// Print error message
oStdErr << "helloworld: takes 0 parameters, but received " << nArgs << "." << std::endl;
// The user may implement more complex behaviour such as internationalization
}
};Example of adding it to a shell instance:
auto pShell1 = bs::shell::make_default_shell();
pShell1->set_command<command_helloworld>();
auto pShell2 = bs::shell::make_default_shell();
auto pCommand = std::make_unique<command_helloworld>();
pShell2->set_command(std::move(pCommand));Example of shell depth check:
// Increase depth
if (!oSession.increase_shell_depth()) {
this->msg_error_max_depth_reached(oSession.err());
return shell_status::SHELL_ERROR_MAX_DEPTH_REACHED;
}
// Run command here
// Decrease depth
oSession.decrease_shell_depth();