< up >
2024-01-30

execute c source files

tl;dr:

  1. add //usr/bin/gcc "$0" && exec ./a.out "$@" to the first line of your example.c
  2. make executable: chmod +x example.c
  3. run it: ./example.c

I’ve a folder called lab for those quick ‘n’ dirty programs to test some language features or other interesting behavior. While golang programs can be easily executed via go run <go file> and this is not the case for c programs needing a built step. There is AFAIK no such command like gcc run.

Shebang

Linux’s execve(2) syscall supports shebangs. When the first line starts with #! aka shebang, it will get parsed by the kernels binfmt_script module and used for executing the rest of the file. This enables to run various non-binary scripts (bash, python, ruby, ) like ./program without using the interpreter.

Can we use a shebang for c source files? tl;dr: nope, as far as I tried this is not possible.

I’ve ended up with something like:

#!/usr/bin/tail -n+2 main.c /tmp/main.c && /usr/bin/gcc /tmp/main.c -o main && ./main #
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
  for(int i = 0;i<argc; i++) {
    printf("%d: %s\n", i, argv[i]);
  }
}
/*
 * $ ./main.c 1 2 3
 * /usr/bin/tail: invalid number of lines: ‘+2 main.c /tmp/main.c && /usr/bin/gcc /tmp/main.c -o main && ./main #’
 */

This doesn’t work while everything after tail gets interpreted as one argument.

binfmt fallback

Just before I was about to give up, I’ve found an SO post suggesting:

//usr/bin/clang "$0" && exec ./a.out "$@"

I’ve adapted it to my use-case and voila:

//usr/bin/gcc "$0" && ./a.out "$@";exit
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
  for(int i = 0;i<argc; i++) {
    printf("%d: %s\n", i, argv[i]);
  }
}
/* 
 * $ ./main.c 1 2 3
 * 0: ./a.out
 * 1: 1
 * 2: 2
 * 3: 3
 */

Well, I guess this works because after binfmt fails to detect any elf-binary or shebang, bash executes it in the current context, which is actually bash.