Scripting with C

Can C be used as a scripting language? Should it?

So, can C be used as a scripting language?

The short answer is "no".

The longer answer is "not really, but..."

Contents

What is a scripting language?

According to Wikipedia, a scripting language is a language used to customise or automate the utilities of the existing system. It is usually interpreted at runtime, rather than compiled, and it usually can interact with other programs.

Some widely used scripting languages are Bash, Perl, Python, and even JavaScript (though JavaScript is mostly limited to running within a browser).

Bash in particular is ubiquitous in Linux systems and the default command language for many Linux distributions. It interacts with standard Linux tools to perform a variety of tasks quickly.

It is enough to write in the terminal echo "Hello world" to have the string “Hello world” returned, no intermediate steps required.

Perl can also be used to print strings in the terminal with the -e command line option: perl -e 'print("Hello world\n")' will print "Hello world". In Python, the equivalent command line option would be -c, as in: python -c 'print("Hello world")'

However, Perl and Python scripts are not usually called with the program passed as a string. They usually live in executable source code files that begin with a shebang: #!/usr/bin/env python for Python, or #!/usr/bin/env perl for Perl. The script can be executed either by calling the interpreter to read it (for example, python myscript.py), or by directly executing the source code file (./myscript.pl). In the latter case, the kernel will spawn a new process with the interpreter referenced by the shebang, and the file content will be passed to standard input.

A shebang for C

Can we use a shebang for a C source code file? The answer to this is a "yes, but...".

A C source file must be compiled to machine code in order to run it. However, the Tiny C compiler, also known as tcc, can be invoked from scripts. If we include a shebang of the form #!/usr/bin/tcc -run in our source code and execute the file without compiling it first, the tcc compiler will compile the source on the fly and directly execute the program:

#!/usr/bin/tcc -run
#include <stdio.h>

int main(void) {
    printf("Hello world\n");

    return 0;
}

If we make the file executable and run it, it will print "Hello world" in the terminal without producing an executable file: tcc with the -run option compiles to memory and immediately executes the instruction. It's worth noting here that if we try to compile the file with another compiler, such as gcc, the preprocessor will complain for the shebang.

tcc with the -run option can be used without even a source file:

echo 'main(){printf("Hello world\n");}' | tcc -run -
# outputs a warning for not including the
#stdio library and then prints

# Hello world

The C interpreter

There are quite a few C interpreters, as I found out in these StackOverflow and StackExchange questions: Is there an interpreter for C? and Is there a scripting language with C-like syntax?.

The most comprehensive one appears to be Cling, a part of CERN's root project. As far as I can tell, they used to use CINT, also available as a standalone package, and they switched at some point in 2013, and the original developer stopped updating it soon after.

The one I actually tried though is bic.

bic

Bic is a C interpreter and API explorer, according to its author, using a read-eval-print loop.

The installation is fairly straight-forward, clone the repo and then:

autoreconf --force --install
./configure --enable-debug --prefix=/home/USER/mylocaldir
make
make install

Or see if it offers binaries for your distro.

Then, time for some interactive fun:

./bin/bic
BIC> #include <stdio.h>
BIC> char a;
a
BIC> scanf("%c", &a);
H
1
BIC> printf("%X\n", a);
48
3
BIC>

In the above example we run, line by line the following program, that takes a char as an input and returns the hexadecimal representation of this char:

#include <stdio.h>

int main(void) {
    char a;
    scanf("%c", &a);
    printf("%X\n", a);

    return 0;
}

In the bic prompt, there is no need for a main function; actually if we try to type int main(void) { and press <ENTER> it will throw an error. In that sense, it doesn't work like the familiar Python interpreter, or even the Bash shell, which allow us to write functions line by line.