-
Notifications
You must be signed in to change notification settings - Fork 3
Using ccsh
Note: all these types and functions are within ccsh namespace, so imagine a using namespace ccsh; at the beginning of each snippet.
First let's see how to run a simple exacutable like ls.
shell("ls");The shell function executes a native binary which may be an absolute, relative or found in PATH.
You can give parameters to the program using the second argument of shell. Its type is std::vector<std::string>, so an initializer list comes handy.
shell("ls", {"-l"});To understand the behavior of running commands, we need to go into implementation details a bit. The pipe or redirection operators return a temporary command, that you can store:
command c = shell("ls");If you do nothing to the command, it will be executed in its destructor, so the syntax will be more bash-like. This is provided only for syntactic sugar, but it has a few disadvantages compared to example b).
a)
shell("ls");However, if you do anything to a command, e.g. store it, pass it to a function or run it, it won't be run automatically, you need to call .run() on it.
b)
command c = shell("ls");
c.run();If you don't call .run(), like in example a), commands will behave more like bash:
- Error messages will be written to stderr.
- Its return value can be accessed via
command::last_exit_code().
If you explicitly call .run(), the behavior fits better for C++.
- If any error occures, an instance of
stdc_errorwill be thrown. -
.run()function returns the exit code.
The implementation of this syntactic sugar is tricky, and has some drawbacks:
- Storing a command using
autowon't work.
auto c = shell("ls"); // ERROR- Storing a command using C++'s "extending life of a temporary" feature will result in unexpected behavior. Do NOT write the following!
auto const& c = shell("ls"); // COMPILES, UNEXPECTED BEHAVIORccsh gives a lot of possibilites to chain commands or redirecting input / output. Actually, it is intended to give you all the possibilities of bash. Currently all bash features are implemented, except >(command) and background jobs.
The following features are implemented:
- Pipe commands or functors
- Logical chaining
- Command substitution
- Redirect commands to file
- Redirect commands in memory
- Redirect commands to functors
All these operations return a command, just like shell, with the same conditions.
Piping is simple: you can pipe commands using operator|.
shell("ls") | shell("cowsay");In addition to commands, you can also pipe special functors called command_functor.
using command_functor = std::function<int(int, int, int)>;This is the pipeable functor in ccsh. These functors shall return an int like main, and the three int arguments are file descriptors to their stdin, stdout and stderr. For example:
auto f = [&](int, int out, int)
{
ofdstream output{out};
// example string from fortune
output << "Your lucky number is 3552664958674928. Watch for it everywhere." << std::endl;
return 0;
};
f | shell("cowsay");These functors are designed for pipes, but you can make a standalone command from a command_functor using command_make function, the return value is usable anywhere just like any command.
You may use &&, || and , operators between commands or bool values (true or false), their return values are the same as if you ran them in shell. Operator comma stands for ; in bash.
command c = shell("ls") || true;
std::cout << c.run(); // always 0 because of "true"You can get the stdout of a command as a string using dollar:
std::string files = dollar(shell("ls"));If the command fails, dollar throws an exception with stderr as message. Otherwise, stderr is lost.
Note: you may use $ instead of dollar, see environment variables for details.
You can redirect the stdin, stdout or stderr to a file using the following operators:
-
<stands for input redirection -
>stands for output redirection -
>>stands for output appending -
>=stands for error redirection -
>>=stands for error appending
The right hand side arguments are fs::path, see Filesystem for details. Example:
shell("ls") > "files.txt";You can redirect everything to either a std::string or a std::vector<std::string> with the same operators as file redirection uses.
Important: input redirection consumes the source, so you can check if all the input was read, etc. Output redirections clean the target before executing, but appending does not clean.
There are two types of redirectable functors. Only classic redirecting operators (<, >, >=) are supported, appending isn't, they would be meaningless.
using command_functor_raw = std::function<ssize_t(char*, std::size_t)>;
using command_functor_line = std::function<void(std::string const&)>;Line functors are called after each line break, with the whole line (without '\n') as their argument. Input redirection from a line functor is not supported as it would be meaningless, see Pipe functors for input redirection.
Raw functors behave like write or read. They are mostly used internally, and hard to use, but also provided in the public API, because why not.
ccsh can be compiled either with boost::filesystem or std::filesystem, whatever your system has. There is a compatibility namespace named ccsh::fs above native namespace, it is recommended to use this.
In ccsh strings and paths (ccsh::fs::path) are semantically different. Paths can be constructed from a std::string or C-string, so overload resolution and readability may be ambiguous. For this reason, a literal operator called _p is provided in namespace ccsh::literals.
shell("ls"_p) > "files.txt"_p;