/c99sh

A shebang-friendly script for "interpreting" single C99, C11, and C++ files, including rcfile support.

Primary LanguageShellBSD 2-Clause "Simplified" LicenseBSD-2-Clause

c99sh

Basic Idea

A shebang-friendly script for "interpreting" single C99, C11, and C++ files, including rcfile support. Build Status

For example, installing this ~/.c99shrc control file

-Wall -g -O2
#include <stdio.h>

permits executing hello containing

#!/usr/bin/env c99sh
int main()
{
    puts("Hello, world!");
}

to produce the output one expects provided c99sh is in the path. You may also run c99sh foo.c to execute some foo.c lacking the shebang line. Try c99sh -v foo.c if you encounter trouble and want to see the compilation command. Check out c99sh -h for all the command line options you might use. In particular, for simple tasks you might find that the command line options in conjunction with HERE documents can accomplish many things. For example,

$ ./c99sh -sm <<HERE
puts("Hello, world!");
HERE

One or more lines can be included using -e:

$ ./c99sh -e 'int main()' -e '{}'

Usually, -sm appears alongside -e:

$ ../c99sh -e 'int start = 3;' -sm <<HERE
if (start == 3) {
    printf("Hello from 1-liner\n");
} else {
    return 1;
}
HERE

Beware quote escaping for -e could use some printf love. Patches welcome.

Control Files

Control files can supply compilation and linking flags, preprocessor directives like #include, and pkg-config directives to simplify library usage. A c99shrc located in the same directory as the interpreted source will be used. Otherwise a ~/.c99shrc is processed if available. See c99shrc.example for an extended control file enabling GSL, GLib, and SQLite capabilities. Control files ease accessing libraries with higher-level data structures.

A more entertaining example is an OpenMP-enabled Monte Carlo computation of π screaming like a banshee on all your cores (c99shrc, source):

#!/usr/bin/env c99sh

int main(int argc, char *argv[])
{
    long long niter = argc > 1 ? atof(argv[1]) : 100000;
    long long count = 0;

    #pragma omp parallel
    {
        unsigned int seed = omp_get_thread_num();

        #pragma omp for reduction(+: count) schedule(static)
        for (long long i = 0; i < niter; ++i) {
            const double x = rand_r(&seed) / (double) RAND_MAX;
            const double y = rand_r(&seed) / (double) RAND_MAX;
            count += sqrt(x*x + y*y) < 1;
        }

    }

    printf("%lld: %g\n", niter, M_PI - 4*(count / (double) niter));
}

Take that, GIL.

Kidding aside, the speedup in the edit-compile-run loop can be handy during prototyping or analysis. It is nice when useful one-off scripts can be moved directly into C ABI code instead of requiring an additional {Python,Octave,R}-to-C translation and debugging phase. For example, compare the Octave version of some simple logic with the equivalent c99sh-based version requiring only a few one-time additions to your ~/.c99shrc.

Shebang Tricks

Dual shebang/compiled support, that is a source file that can be both interpreted via ./shebang.c and compiled via gcc shebang.c, can most succinctly be achieved as follows:

#if 0
exec c99sh "$0" "$@"
#endif

#include <stdio.h>

int main(int argc, char *argv[])
{
    for (int i = 1; i < argc; ++i) {
        printf("Hello, %s!\n", argv[i]);
    }
}

This dual shebang approach permits quick testing/iteration on valid C source files using the -t option:

#if 0
exec c99sh -t 'test()' "$0" "$@"
#endif

#include <stdio.h>

int logic()
{
    return 42;
}

static void test()
{
    printf("%d\n", logic());
}

Testing in this manner resembles how folks use Python's __main__ inside libraries.

C++

As nearly the entire C99-oriented implementation works for C++, by invoking c99sh through either a copy or symlink named cxxsh, you can write C++-based logic. The relevant control files are named like cxxshrc in this case and they support directives like using namespace std and namespace fb = foo::bar. See cxx/hello and cxx/cxxshrc for a hello world C++ example. See cxx/shebang.cpp and cxx/quicktest.cpp for C++ dual shebang/compiled idioms.

One nice use case is hacking atop Eigen since it provides pkg-config support. That is, cxxsh -p eigen3 myprogram builds and runs a one-off, Eigen-based program. With the right cxxshrc, such a program can be turned into a script. Though, you will likely notice the compilation overhead much moreso with C++ than C99. That said, for repeated invocation an output binary can be saved with the -x option should repeated recompilation be prohibitively expensive.

C11

C11 can be used via a symlink named c11sh with control files like c11shrc.

Credits

The idea for c99sh came from 21st Century C's section "Compiling C Programs via Here Document" (available online) by Ben Klemens. Additionally, I wrote it somewhat in reaction to browsing the C++-ish work by elsamuko/cppsh.

The dual shebang/compiled approach was suggested by mcandre and jtsagata. Thank you both for pushing on the idea, as I did not think it could be done in three clean lines.

The one line execution similar to Perl's -e was done by mattapiroglu.

The -l command line option was contributed by flipcoder.

The -F and -L command line options were contributed by ProducerMatt.