Write C++ like it's Python or Ruby. Execute instantly, debug natively, and deploy as zero-dependency binaries.
cppsh is a lightweight, dependency-free wrapper that allows you to write modern C++23 scripts without the boilerplate. It transparently handles compilation, execution, linking, and crash handling, giving you the ergonomics of a dynamic scripting language with the blazing speed and type-safety of native C++.
- Bash is great for 10-line scripts, but becomes an unmaintainable nightmare of blacklists and string-parsing once your logic grows complex.
- Python/Ruby have excellent ergonomics, but deploying them requires shipping massive interpreters, managing
pipvirtual environments, and hoping the target server has the right dependencies. - Standard C++ is blazingly fast and safe, but writing a simple script requires setting up
CMakeLists.txt, writingint main(), and manual compiling just to test a 5-line idea.
cppsh gives you the best of all worlds:
Write top-level C++ statements in a single file, run it instantly via a shebang (#!/usr/bin/env cppsh), and when you are ready for production, compile it into a single, standalone executable that runs anywhere.
- Interactive REPL: Run
cppshwith no arguments to launch an interactive C++ prompt with state retention and auto-printing. - Hot-Reloading (
--watch): Perfect for web development.cppshwatches your script file and automatically recompiles and restarts your server whenever you save changes. - Zero Boilerplate: No
int main(), no mandatory#include <iostream>. Just start writing code. - Shebang Support: Run your C++ files directly:
./script.cppsh - Ruby-Style Shell Commands: Execute OS commands trivially using backticks: `ls -la`. You can also use shell interpolation, e.g.,
`ls -la {dir}`. - Java-Like Stack Traces: If your script crashes (e.g., Segfault, Out of Bounds),
cppshautomatically catches the OS signal and prints a beautiful C++23 stack trace mapped to your script's line numbers. - Native Debugging: Run
cppsh --debug script.cppshto drop directly intogdborlldb. Breakpoints map exactly to your script. - AOT Deployment: Run
cppsh --build script.cppshto generate a heavily optimized (-O3) standalone binary. - Export to IDE: Run
cppsh --export script.cppshto generate a clean, standard.cppfile when your script outgrows the single-file format. - Magic Comments: Declare linker dependencies (
// import ssl) or compiler overrides directly in your script. - Zero-Latency Execution: Uses SHA-1 content hashing to cache compiled binaries. If the script hasn't changed, execution is near-instant.
- Include Hoisting: No more jumping to the top of the file. Place
#includeanywhere;cppshautomatically hoists them to the global scope. - Modern Prelude: Includes
prelude.hppaliases (str,vec,u64) for concise coding. - Safe Shell Escaping: Escapes special characters in variables used in shell commands to prevent injection.
Because cppsh heavily leverages bleeding-edge standard library features (like <print>, <ranges>, and <stacktrace>), you need a modern C++ compiler.
- OS: Linux or macOS
- Compiler:
clang++(version 18+) org++(version 14+) - Standard: C++23 support is required.
cppsh is self-contained. You just need to compile the core engine and place it in your path.
# 1. Clone the repository
git clone https://github.com/tcmak/cppsh.git
cd cppsh
# 2. Compile the engine (Requires a C++23 compiler)
# For Clang users:
clang++ -std=c++23 -O3 cppsh.cpp -o cppsh
# For GCC users (Requires g++ 14 or newer):
g++ -std=c++23 -O3 cppsh.cpp -o cppsh
# 3. Move it to your path
sudo mv cppsh /usr/local/bin/You can configure cppsh globally via environment variables in your ~/.bashrc or ~/.zshrc:
# Set your preferred compiler (defaults to clang++ or g++)
export CXX="clang++"
# Set your preferred debugger (defaults to gdb. macOS users should use lldb)
export CPPSH_DEBUGGER="lldb"
# Global Compiler Flags (e.g., enable all warnings)
export CPPSH_CXXFLAGS="-Wall -Wextra"
# Global Linker Flags
# NOTE: cppsh defaults to '-lstdc++_libbacktrace' on Linux to provide
# Java-like stack traces when scripts crash.
# If your specific compiler/OS (e.g., custom CentOS builds, macOS) fails
# with "cannot find -lstdc++_libbacktrace", clear this variable:
export CPPSH_LDFLAGS=""cppsh operates in two modes: Interactive REPL and Script Runner.
Simply type cppsh in your terminal to start experimenting with C++ instantly. Expressions are automatically evaluated and printed.
$ cppsh
=== Welcome to cppsh (C++23 REPL) ===
Type `cmd` to run a shell command.
Type exit or quit to leave.
cppsh> auto name = "Modern C++";
cppsh> name
Modern C++
cppsh> `uptime`
14:22:15 up 2 days, 3:14, 1 user, load average: 0.12, 0.05, 0.01
cppsh> exitCreate a file named hello.cppsh:
#!/usr/bin/env cppsh
std::println("Hello, Modern C++!");
if (args.size() > 1) {
std::println("You passed: {}", args);
}
std::println("Current system uptime:");
`uptime`
str folder = "my documents";
`ls -la {folder}`; // Transpiles to safe, quoted: ls -la 'my documents'Make it executable and run it:
chmod +x hello.cppsh
./hello.cppsh "Test Argument"If you are building a C++ web server (using a micro-framework like httplib or crow), use the watch flag. Every time you save the file, cppsh instantly recompiles and restarts the server.
cppsh --watch server.cppshYou can instruct cppsh to link against system libraries or use specific compilers just by adding comments to the top of your script.
#!/usr/bin/env cppsh
// cppsh_compiler: g++-14
// import pthread
// import ssl
std::println("This guarantees compilation with g++-14 and links libpthread and libssl.");Any line wrapped in backticks is executed as a shell command, and its output is printed directly to stdout. Under the hood, this compiles down to a safe, memory-managed POSIX pipe.
#!/usr/bin/env cppsh
std::println("Cleaning up temp files...");
`rm -rf /tmp/cache_*`
std::println("Done.");When a script goes wrong, you don't need to manually recreate a CMake project to debug it. Just pass --debug. cppsh uses #line directives to perfectly map the compiled binary back to your original script file.
cppsh --debug my_script.cppsh(This will launch gdb or lldb. You can type break my_script.cppsh:10 and it will work flawlessly!)
This is where cppsh destroys Python. Once your script is tested, you can compile it into a highly optimized (-O3), standalone native binary.
cppsh --build data_pipeline.cppshOutput:
[cppsh] Successfully built optimized deployment binary: ./data_pipeline
You can now scp the ./data_pipeline executable to a server that doesn't have cppsh, clang, or any libraries installed, and it will run natively at maximum hardware speed.
If your script grows to thousands of lines and needs to be migrated into a proper CMake project with multiple files and headers, you don't have to rewrite it. Use the --export flag.
cppsh --export my_script.cppshOutput:
[cppsh] Successfully exported proper C++ source to: ./my_script.cpp
You can now open this file in your IDE (CLion, VS Code, etc.).
This generates a clean, 100% standard C++ file (my_script.cpp) in your current directory. It automatically injects the int main() boilerplate, the run_shell implementation for your backticks, and the <stacktrace> crash handlers, but intentionally strips out the #line mappings so your IDE debugger steps through the newly exported .cpp file perfectly!
In standard C++, you'd have to scroll to line 1 to add a header. In cppsh, you can stay in your flow. The transpiler scans for #include or import and pulls them to the global scope for you.
#!/usr/bin/env cppsh
auto start = std::chrono::steady_clock::now();
#include <chrono> // This is hoisted to the top automatically!
#include <thread>
std::this_thread::sleep_for(std::chrono::seconds(1));To make C++ feel more like a scripting language, cppsh provides a lightweight prelude of aliases and helpers. No more typing std::vector<std::string>.
#!/usr/bin/env cppsh
// 'str' and 'vec' are part of the built-in prelude
vec<str> lines = {"one", "two", "three"};
for (const auto& line : lines) {
std::println("Processing: {}", line);
}
u64 big_num = 0xFFFFFFFFFFFFFFFF; // 'u64' alias for uint64_tWhen you run cppsh script.cppsh, the engine:
- Parses your script to extract
#includedirectives and// importflags. - Injects a hidden crash-handler for
<stacktrace>support. - Wraps your raw statements in a hidden
int main(int argc, char* argv[])block. - Unpacks
argvinto a modernstd::vector<std::string> args. - Hash Check: Generates a SHA-1 hash of your script. If a matching binary exists in ~/.cache/cppsh/, it executes immediately.
- Transpiles `shell commands`
- Hoists #include lines.
- Wraps code in int main().
- Injects Prelude and _cppsh_sh() helpers.
- Writes the valid C++ file to
/tmp/cppsh_script.cpp. - Compiles it with
-O0for near-instant startup time (or-O3if using--build). - Executes the binary and streams the output to your terminal. Replaces the engine process with the script binary using execvp.
Contributions are welcome! Please ensure you run the lightweight, built-in test suite before submitting a PR:
# Using Clang:
clang++ -std=c++23 cppsh_test.cpp -o run_tests && ./run_tests
# Using GCC:
g++ -std=c++23 cppsh_test.cpp -o run_tests && ./run_tests