
C – Compile and execute C “scripts” in one go - ryanmjacobs
https://github.com/ryanmjacobs/c
======
lambda
You don't need to install anything; you could put this on the first line of
your file, and achieve the same effect, with just tools you already have
installed:

    
    
      //usr/bin/make -s "${0%.c}" && ./"${0%.c}" "$@"; s=$?; rm ./"${0%.c}"; exit $s
    

Actually, you could extend this to any file type that Make has built-in rules
for, and which uses // as a comment delimiter:

    
    
      //usr/bin/make -s "${0%.*}" && ./"${0%.*}" "$@"; s=$?; rm ./"${0%.*}"; exit $s

~~~
htor
I applaud you. This must be the best hack I've seen so far.

~~~
placeybordeaux
Make's intended purpose is to chain commands in a logical fashion. ;'s
intended purpose is to separate lines to execute.

How is this a hack?

~~~
lambda
It's a hack because it's relying on the fact that without a shebang line or
the appropriate magic number for your executable format, most platforms
interpret scripts as a shell script; and it's using the fact that // can be
treated as a comment delimiter in C or as a redundant way of referring to / in
shell. So we interpret the first line as a shell script which calls out to
make, to build the input file, then execute it, then exit so we don't try
interpreting the rest of the C source as shell; and that first line of shell
is interpreted as a comment in C.

~~~
placeybordeaux
Apparently I had a reading comprehension failure. I thought he was just saying
that you can use that as an alias instead of downloading a new command. That
is neat.

------
cnvogel
I find this construct quite interesting....

    
    
        help_msg() {
           >&$1 echo "Usage: $0 [file.c...
           >&$1 echo "Execute C progams from the command line."
           ...
        }
    

for that it puts the redirection at the beginning of the line, which is
unusual and I didn't even realize until now that it's valid. ( example: >&55
redirects stdout to filescriptor number 55, and here >&$1 redirects stdout of
echo to the filedescriptor number given as the first argument to the function)

    
    
        # help if we have no arguments and no stdin
        if [ $# -eq 0 ] && [ -t 0 ]; then
            help_msg 2  # <--- NOTE 2 = stderr
            exit 1
        fi
    
        # help if we get the flags
        if [ "$1" == "--help" ] || [ "$1" == "-h" ]; then
            help_msg 1  - <--- NOTE 1 = stdout
            exit 0
        fi
    

And second, that the author seems to switch between outputting the help_msg on
stdout or stderr, depending on if stdout exists. I always was under the
impression that only the actual script result ought to go to stdout, and
personally I always put out general debugging, error messages, but also the
usage, unconditionally to stderr.

~~~
bobbyi_settv
He's following the philosophy that says when you run "program --help", the
expected output of the program is the usage info and therefore it should go to
stdout, but when you run "program <invalid args>" and it prints the same usage
info, that is an error message and should go to stderr.

------
rlonstein
Neat hack, but if I wanted to compile, I'd just go ahead and compile. These
are more interesting:

    
    
      http://bellard.org/tcc/ (use -run)
    
      https://root.cern.ch/drupal/content/cint
      https://root.cern.ch/drupal/content/cling

~~~
fiorix
tcc takes it even further by being used as a library in your c programs, so
they can run c scripts. [http://gwan.com](http://gwan.com) does this.

------
sdsk8
Congratulations, but [http://bellard.org/tcc/](http://bellard.org/tcc/)
already do that!

How do they compare?

~~~
audunw
This is just a script, that invokes whichever compiler is set in the "CC"
environment variable. If you set CC=tcc, it's basically the same as "tcc -run
file.c", but I suppose this is a little bit nicer: "c file.c".

I would probably use this with Clang rather than TCC if Clang compiles fast
enough, which it generally does.

------
DSMan195276
A quick note, I see a few big issues:

1\. There's no guarantee that you can run anything directly out of /tmp/. IIRC
lots of distros mount /tmp/ with noexec specifically so you can't do this. You
might still be able to invoke ld directly to run it, but that's still kinda a
hack to get around the noexec.

2\. You need write access to the .c file. That means you can't install any
scripts using this system-wide, because you won't have write-access to the .c
source unless you're root.

IMO, the most obvious solution to the second is to make a copy of the .c
source and edit that instead. AFAIK there isn't an easy solution to the first
issue though.

------
0942v8653
This doesn't appear to cache compiled "scripts", which to me makes it kind of
useless. It's nice not to leave binaries laying around but I'd expect things
to be saved (otherwise it's pretty slow).

~~~
maffydub
It would probably be quicker if it cached, but don't underestimate the speed
of gcc...

A year or so ago, as part of my work on Project Clearwater
([http://www.projectclearwater.org/](http://www.projectclearwater.org/)), I
was using a Ruby tool to retrieve statistics from a server, plumbing them into
Cacti ([http://www.cacti.net/](http://www.cacti.net/)) and graphing the
results.

Cacti likes to restart statistics-gathering processes every time it wants new
statistics (once a minute in my system), so this meant starting the Ruby
interpreter every minute.

Project Clearwater is built to be scalable, so I turned up a few hundred nodes
(EC2 makes this easy), at which point Cacti couldn't keep up - it took the
best part of a second per Ruby process invocation, and since I was polling
every minute, there just wasn't enough time to get through all the nodes.

I rewrote the Ruby tool in C++, at which point it ran in less than 0.1s, which
was fast enough for what I needed.

Amusingly (at least to me), it actually _compiled_ (under GCC) and ran in less
time than it took for the Ruby interpreter to start.

(This is not intended to be a comparison of the merits of C++ and Ruby. It's
quite possible the Ruby code could have been optimized and really I was
solving the wrong problem - I probably should have been making the statistics-
gathering process long-lived. The point of the above is just that GCC is
actually very quick for relatively small programs.)

Matt

------
kazinator
Here is a way to do it without installing a /usr/bin/c.

All you takes is a few lines of shell code to the top of the C file.

[http://rosettacode.org/wiki/Multiline_shebang#C](http://rosettacode.org/wiki/Multiline_shebang#C)

I contributed that, anonymously. Previously, the task had been marked "omit
from C", would you believe it!

Also, note the little "Student exercise" below the code. For this to be
useful, you want to cache the compiler output; you want to recompile the
underlying executable only if the C script has changed.

The inconvenience of invoking C programs obviously isn't the real obstacle to
its use as a scripting language, otherwise this kind of thing would be widely
used.

------
hawski
I have done something like this and use it for simple tests of my assumptions
[1]. This one from OP is more polished I suppose. I must check if it supports
(with shebang) putting "c-script",in pipeline.

As was already mentioned Fabrice Bellard's tcc is great in this regard. There
were similar projects done with LLVM.

What I would really like to have is some kind of compiler or different C
preprocessor that would implement modules, such as that building C would be as
simple as building Go programs. Price for it I suppose are macros. I think
it's possible.

[1] [http://hawski.com/ccrun](http://hawski.com/ccrun)

------
0x09
I like lli for quickly running some code (no temporaries!)

    
    
      $ clang -xc -c -emit-llvm -o - - | lli
         #include <stdio.h>
         int main(void) {
            puts("hi");
         }^D
      hi
    

As a shell executable interpreter

    
    
      #!/usr/bin/env bash
      [ -f "$1" ] && file="$1" || file=/dev/stdin
      awk 'NR==1&&/^#!/{next}{print}' "$file" | \
       clang -xc -c -emit-llvm $CFLAGS -o - - | \
       lli -fake-argv0="$file" $IFLAGS -
    

You can even "link" static archives

    
    
      $ IFLAGS=-extra-archive=/path/to/libz.a      \
      > CFLAGS+='-include stdio.h -include zlib.h' \
      > c <<< 'int main(void){puts(zlibVersion());}'
      1.2.8

------
kasabali
Beware that using another script as the executable in shebang _may not_ work
everywhere [0]

[0] [http://www.in-
ulm.de/~mascheck/various/shebang/#interpreter-...](http://www.in-
ulm.de/~mascheck/various/shebang/#interpreter-script)

------
mikeash
I do something similar when working on single-file programs, but without any
external dependencies, by making the file both a valid shell script (for my
shell) and a valid C program. Here's an example in Swift, although the same
idea works for C too:

[https://gist.github.com/mikeash/70d74f7b7745cf6fbd3f](https://gist.github.com/mikeash/70d74f7b7745cf6fbd3f)

~~~
lambda
That's pretty complicated; you could just do:

    
    
      //usr/bin/env swift $0 "$@"; exit $?
      println("helloo")

~~~
mikeash
My approach avoids recompiling the program every time you run it, and it also
makes it much easier to debug crashes. At least when I was doing this, running
`swift` directly produced really bad crash info when things went wrong.

------
radiospiel
Also: [https://github.com/radiospiel/jit](https://github.com/radiospiel/jit)

which supports go, c, and flex.

------
1ris
So

alias c=tcc -run

?

~~~
ElDiablo666
I think it executes it as a script, though. Like, it uses a compiler but isn't
itself a compiler.

------
npsimons
Nice to have another option, but been able to do this (as well as C++,
FORTRAN, assembler, Java and Pascal) under Linux for a while:

[https://www.netfort.gr.jp/~dancer/software/binfmtc.html.en](https://www.netfort.gr.jp/~dancer/software/binfmtc.html.en)

For Debian, "apt-get install binfmtc"

------
RhysU
Shameless self plug on alternative:
[https://github.com/RhysU/c99sh](https://github.com/RhysU/c99sh)

Ditto: Shebang, stdin Extras: Automatic includes, nice error reporting, RC
files, pkg-config, and C++ too. Lacks: Multiple file

------
harry8

      #if 0
      THIS=$0
      BIN=/tmp/$(basename $0)
      cc $THIS -Wall -o $BIN
      $BIN
      rm $BIN
      exit
      #else
      #include<stdio.h>
      int main(){printf("hello\n");}
      #endif

------
sigjuice
[https://gist.github.com/khirbat/1471088](https://gist.github.com/khirbat/1471088)

------
coherentpony
Related: [https://github.com/RhysU/c99sh](https://github.com/RhysU/c99sh)

------
ralmeida4381
Interesting... C in your shell is like having a sonic screwdriver in your
pocket.

~~~
frik
Never heard of "C shell"?
[http://en.wikipedia.org/wiki/C_shell](http://en.wikipedia.org/wiki/C_shell)

~~~
frou_dh
Doesn't have much to do with C in practice.

For a fully-realized C shell, see TempleOS with its HolyC variant:
[https://www.youtube.com/watch?v=5gfoDHycEi0](https://www.youtube.com/watch?v=5gfoDHycEi0)

------
to3m
> "We should all write more C."

Erm... speak for yourself :)

