37

So this is probably a long shot, but is there any way to run a C or C++ file as a script? I tried:

#!/usr/bin/gcc main.c -o main; ./main

int main(){ return 0; }

But it says:

./main.c:1:2: error: invalid preprocessing directive #!
Brendan Long
  • 53,280
  • 21
  • 146
  • 188
  • 3
    There are interpreters: cint, ch; plus igcc and tcc will act like one. There are some questions about them around here somewhere: http://stackoverflow.com/questions/69539/have-you-used-any-of-the-c-interpreters-not-compilers http://stackoverflow.com/questions/584714/is-there-an-interpreter-for-c and probably a few others. And this may be a duplicate: http://stackoverflow.com/questions/2277865/are-there-any-low-level-languages-that-can-be-used-in-place-of-scripts – dmckee --- ex-moderator kitten Mar 20 '10 at 08:15

15 Answers15

43

Short answer:

//usr/bin/clang "$0" && exec ./a.out "$@"
int main(){
    return 0;
}

The trick is that your text file must be both valid C/C++ code and shell script. Remember to exit from the shell script before the interpreter reaches the C/C++ code, or invoke exec magic.

Run with chmod +x main.c; ./main.c.

A shebang like #!/usr/bin/tcc -run isn't needed because unix-like systems will already execute the text file within the shell.

(adapted from this comment)


I used it in my C++ script:

//usr/bin/clang++ -O3 -std=c++11 "$0" && ./a.out; exit
#include <iostream>
int main() {
    for (auto i: {1, 2, 3})
        std::cout << i << std::endl;
    return 0;
}

If your compilation line grows too much you can use the preprocessor (adapted from this answer) as this plain old C code shows:

#if 0
    clang "$0" && ./a.out
    rm -f ./a.out
    exit
#endif
int main() {
    return 0;
}

Of course you can cache the executable:

#if 0
    EXEC=${0%.*}
    test -x "$EXEC" || clang "$0" -o "$EXEC"
    exec "$EXEC"
#endif
int main() {
    return 0;
}

Now, for the truly eccentric Java developer:

/*/../bin/true
    CLASS_NAME=$(basename "${0%.*}")
    CLASS_PATH="$(dirname "$0")"
    javac "$0" && java -cp "${CLASS_PATH}" ${CLASS_NAME}
    rm -f "${CLASS_PATH}/${CLASS_NAME}.class"
    exit
*/
class Main {
    public static void main(String[] args) {
        return;
    }
}

D programmers simply put a shebang at the beginning of text file without breaking the syntax:

#!/usr/bin/rdmd
void main(){}

See:

Pedro Lacerda
  • 1,098
  • 9
  • 23
  • Sorry for the rollback. – Pedro Lacerda Apr 20 '15 at 16:28
  • 2
    to pass arguments to executable c++ code, add "$@" after ./a.out;So it would be -------------------------------- //usr/bin/clang++ -O3 -std=c++11 "$0" && ./a.out "$@"; exit – scinart Nov 01 '17 at 07:12
  • 1
    Great answer. I would just also check that the existing binary is not outdated. – Raúl Salinas-Monteagudo Nov 22 '18 at 13:06
  • @RaúlSalinas-Monteagudo in the example 4 it can happen indeed, an `stat -c %y a.out main.c` comparison would ameliorate. Somewhat between 1 and 2 looks production ready, lol. – Pedro Lacerda Feb 21 '19 at 14:32
  • So unix allows both forms of shebang `#!` and `//`. Any idea where this is documented? – humanityANDpeace Jun 19 '19 at 07:40
  • @humanityANDpeace It isn't really a shebang. If there is no shebang and execve fails, Linux will try to run it as shell script. Thoose files are both shell script and C++/Java/whatever source code. The command `//usr/bin/echo` is the same as `/usr/bin/echo`. Fortunately `//` it is also an usual comment string. The idea behind `/*/../usr/bin/echo` is more tricky. The parent of any directory below `/` is the root itself.See https://stackoverflow.com/a/12296783/199332 If it doesn't works for you (some messed Linux system like my own right now) you need some other way. – Pedro Lacerda Sep 30 '19 at 00:02
  • @humanityANDpeace just updated the answer with some references. – Pedro Lacerda Sep 30 '19 at 00:11
  • Apparently a triple backslash in front is "more standards compliant". See [here](https://stackoverflow.com/a/30082862/4561887). For that reason, I used the triple backslash `///` in front [in my answer](https://stackoverflow.com/a/75491834/4561887) too. – Gabriel Staples Feb 18 '23 at 17:55
32

For C, you may have a look at tcc, the Tiny C Compiler. Running C code as a script is one of its possible uses.

Remo.D
  • 16,122
  • 6
  • 43
  • 74
18
$ cat /usr/local/bin/runc
#!/bin/bash
sed -n '2,$p' "$@" | gcc -o /tmp/a.out -x c++ - && /tmp/a.out
rm -f /tmp/a.out

$ cat main.c
#!/bin/bash /usr/local/bin/runc

#include <stdio.h>

int main() {
    printf("hello world!\n");
    return 0;
}

$ ./main.c
hello world!

The sed command takes the .c file and strips off the hash-bang line. 2,$p means print lines 2 to end of file; "$@" expands to the command-line arguments to the runc script, i.e. "main.c".

sed's output is piped to gcc. Passing - to gcc tells it to read from stdin, and when you do that you also have to specify the source language with -x since it has no file name to guess from.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
  • @John Kugelman: What is th need for putting `#!/bin/bash /usr/local/bin/runc` as the first line in `main.c` when you are going to strip it using `sed` anyways? – Lazer Mar 21 '10 at 02:51
  • @eSKay - That line tells the system what program to use to "run" the script. Without it bash will try to interpret the `.c` file as a bash script and bomb out with a syntax error. – John Kugelman Mar 21 '10 at 18:59
  • @John Kugelman: thanks! I get the complete idea now. very clever! – Lazer Mar 21 '10 at 19:09
  • 1
    You should use mktemp instead of a.out otherwise you get problems when running two different C-scripts at the same time... Also, the C-script doesn't compile as a real C program because of the hashbang at the top, though I'm not sure what you can do about that... – Graham Sep 26 '10 at 12:49
  • @Graham well that's why he strips out the #! with `sed` before compiling. – Pointy Sep 26 '10 at 13:17
  • @Hugolpz Nope! The whole purpose of all of this is to get it so you can simply type `./main.c` and run the C file as if it were a shell script. – John Kugelman Jan 18 '15 at 02:53
  • @John: `#!/bin/bash /usr/local/bin/runc` , witty! – Hugolpz Jan 19 '15 at 10:18
10

Since the shebang line will be passed to the compiler, and # indicates a preprocessor directive, it will choke on a #!.

What you can do is embed the makefile in the .c file (as discussed in this xkcd thread)

#if 0
make $@ -f - <<EOF
all: foo
foo.o:
   cc -c -o foo.o -DFOO_C $0
bar.o:
   cc -c -o bar.o -DBAR_C $0
foo: foo.o bar.o
   cc -o foo foo.o bar.o
EOF
exit;
#endif

#ifdef FOO_C

#include <stdlib.h>
extern void bar();
int main(int argc, char* argv[]) {
    bar();
    return EXIT_SUCCESS;
}

#endif

#ifdef BAR_C
void bar() {
   puts("bar!");
}
#endif

The #if 0 #endif pair surrounding the makefile ensure the preprocessor ignores that section of text, and the EOF marker marks where the make command should stop parsing input.

Ephphatha
  • 586
  • 2
  • 8
7

CINT:

CINT is an interpreter for C and C++ code. It is useful e.g. for situations where rapid development is more important than execution time. Using an interpreter the compile and link cycle is dramatically reduced facilitating rapid development. CINT makes C/C++ programming enjoyable even for part-time programmers.

Eli Bendersky
  • 263,248
  • 89
  • 350
  • 412
  • Interesting. I'm hoping for something that would have the same result as gcc $stuff; ./filename though. This is already more than I was expecting though. – Brendan Long Mar 20 '10 at 07:52
  • Brendan - If you want to do that you are going to have to write a script in bash or such to compile and call the program. – zellio Mar 20 '10 at 07:53
  • @Brendan: why do you need this, really? To me it doesn't make much sense using C and C++ this way – Eli Bendersky Mar 20 '10 at 07:56
  • No real need, I was just wondering. It would be a funny way to distribute a program. – Brendan Long Mar 20 '10 at 08:00
6

You might want to checkout ryanmjacobs/c which was designed for this in mind. It acts as a wrapper around your favorite compiler.

#!/usr/bin/c
#include <stdio.h>

int main(void) {
    printf("Hello World!\n");
    return 0;
}

The nice thing about using c is that you can choose what compiler you want to use, e.g.

$ export CC=clang
$ export CC=gcc

So you get all of your favorite optimizations too! Beat that tcc -run!

You can also add compiler flags to the shebang, as long as they are terminated with the -- characters:

#!/usr/bin/c -Wall -g -lncurses --
#include <ncurses.h>

int main(void) {
    initscr();
    /* ... */
    return 0;
}

c also uses $CFLAGS and $CPPFLAGS if they are set as well.

Brock Adams
  • 90,639
  • 22
  • 233
  • 295
ryanmjacobs
  • 575
  • 1
  • 5
  • 9
  • Note that an anonymous suggested edit claims: "It now supports caching. After running a script once, the second time will be practically instant." – Brock Adams Jan 01 '16 at 05:04
6
#!/usr/bin/env sh
tail -n +$(( $LINENO + 1 )) "$0" | cc -xc - && { ./a.out "$@"; e="$?"; rm ./a.out; exit "$e"; }

#include <stdio.h>

int main(int argc, char const* argv[]) {
    printf("Hello world!\n");
    return 0;
}

This properly forwards the arguments and the exit code too.

Tamás Zahola
  • 9,271
  • 4
  • 34
  • 46
4

Quite a short proposal would exploit:

  • The current shell script being the default interpreter for unknown types (without a shebang or a recognizable binary header).
  • The "#" being a comment in shell and "#if 0" disabling code.

    #if 0
    F="$(dirname $0)/.$(basename $0).bin"
    [ ! -f $F  -o  $F -ot $0 ] && { c++ "$0" -o "$F" || exit 1 ; }
    exec "$F" "$@"
    #endif
    
    // Here starts my C++ program :)
    #include <iostream>
    #include <unistd.h>
    
    using namespace std;
    
    int main(int argc, char **argv) {
        if (argv[1])
             clog << "Hello " << argv[1] << endl;
        else
            clog << "hello world" << endl;
    }
    

Then you can chmod +x your .cpp files and then ./run.cpp.

  • You could easily give flags for the compiler.
  • The binary is cached in the current directory along with the source, and updated when necessary.
  • The original arguments are passed to the binary: ./run.cpp Hi
  • It doesn't reuse the a.out, so that you can have multiple binaries in the same folder.
  • Uses whatever c++ compiler you have in your system.
  • The binary starts with "." so that it is hidden from the directory listing.

Problems:

  • What happens on concurrent executions?
Raúl Salinas-Monteagudo
  • 3,412
  • 1
  • 24
  • 22
  • I was unaware until know about that omitting a shebang falls back to `/bin/sh`, is this known by experience or a spec? – humanityANDpeace Jun 19 '19 at 08:05
  • According to exec's man: "If the header of a file isn't recognized (the attempted execve(2) failed with the error ENOEXEC), these functions will execute the shell (/bin/sh) with the path of the file as its first argument. (If this attempt fails, no further searching is done.)" That means, it is acceptable and done by the system call execve() itself. What you happen to have in /bin/sh, you don't know. – Raúl Salinas-Monteagudo Jun 19 '19 at 14:26
3

Variatn of John Kugelman can be written in this way:

#!/bin/bash
t=`mktemp`
sed '1,/^\/\/code/d' "$0" | g++ -o "$t" -x c++ - && "$t" "$@"
r=$?
rm -f "$t"
exit $r


//code
#include <stdio.h>

int main() {
    printf("Hi\n");
    return 0;
}
ony
  • 12,457
  • 1
  • 33
  • 41
  • This is more than a variation. its a single file implementation and looks nice. The main problem I have with it is that if you load it in a C++ IDE, it will go berserk. you can't hide the shebang, but if you add `: /*` after the shebang and `#*/` after the `exit` command, then the IDE will ignore all the other bash stuff. – Guss Dec 19 '16 at 12:55
  • @Guss, having shebang look like `#!/bin/bash : /*` results in error looking for a file `: /*` because command line now will look as `/bin/bash : /* ./man.c`. Moreover I believe some IDE will report failure for inproper pre-processor instruction `#!/bin/bash ...`. – ony Dec 20 '16 at 17:53
  • sorry for not being clear enough in my comment. I meant adding `: /*` as **the following line** after the shebang line. Regarding IDEs not liking the shebang (or the next line that start with `:` - yes, probably. The IDEs I normally use mark these as errors but don't make a fuss other than that and the rest of the file is parsed correctly. – Guss Dec 21 '16 at 13:54
  • @Guss, true. That will mark most of the bash code as a comment. Use of `#if 0` and `#endif` as suggested by @Ephphatha will work also. – ony Dec 22 '16 at 15:49
  • True, but then there's no point in hiding the shell code from the compiler as you do with the `sed` expression - you can just give gcc the entire file (minus the shebang). The header then might look like this (note the "\n"s in the text, comments are not line oriented): `#!/bin/bash\n#if 0\n t=$(mktemp -u);g++ -o $t -x c++ <(tail -n+2 $0) && $t "$@"; r=$?; rm -f $t; exit $r\n#endif` – Guss Dec 24 '16 at 13:44
  • @Guss, I said nothing about `sed`. I just pointed to the fact that both `#if 0`/`#endif` and `/* .. */` will work fine to hide some code from most of IDEs. – ony Dec 25 '16 at 17:19
2

Here's yet another alternative:

#if 0
TMP=$(mktemp -d)
cc -o ${TMP}/a.out ${0} && ${TMP}/a.out ${@:1} ; RV=${?}
rm -rf ${TMP}
exit ${RV}
#endif

#include <stdio.h>

int main(int argc, char *argv[])
{
  printf("Hello world\n");
  return 0;
}
d99kris
  • 425
  • 4
  • 8
2

I know this question is not a recent one, but I decided to throw my answer into the mix anyways. With Clang and LLVM, there is not any need to write out an intermediate file or call an external helper program/script. (apart from clang/clang++/lli)

You can just pipe the output of clang/clang++ to lli.

#if 0
CXX=clang++
CXXFLAGS="-O2 -Wall -Werror -std=c++17"
CXXARGS="-xc++ -emit-llvm -c -o -"
CXXCMD="$CXX $CXXFLAGS $CXXARGS $0"
LLICMD="lli -force-interpreter -fake-argv0=$0 -"
$CXXCMD | $LLICMD "$@" ; exit $?
#endif

#include <cstdio>

int main (int argc, char **argv) {
  printf ("Hello llvm: %d\n", argc);
  for (auto i = 0; i < argc; i++) {
    printf("%d: %s\n", i, argv[i]);
  }
  return 3==argc;
}

The above however does not let you use stdin in your c/c++ script. If bash is your shell, then you can do the following to use stdin:

#if 0
CXX=clang++
CXXFLAGS="-O2 -Wall -Werror -std=c++17"
CXXARGS="-xc++ -emit-llvm -c -o -"
CXXCMD="$CXX $CXXFLAGS $CXXARGS $0"
LLICMD="lli -force-interpreter -fake-argv0=$0"
exec $LLICMD <($CXXCMD) "$@"
#endif

#include <cstdio>

int main (int argc, char **argv) {
  printf ("Hello llvm: %d\n", argc);
  for (auto i = 0; i < argc; i++) {
    printf("%d: %s\n", i, argv[i]);
  }
  for (int c; EOF != (c=getchar()); putchar(c));
  return 3==argc;
}
bogen85
  • 83
  • 6
1

There are several places that suggest the shebang (#!) should remain but its illegal for the gcc compiler. So several solutions cut it out. In addition it is possible to insert a preprocessor directive that fixes the compiler messages for the case the c code is wrong.

#!/bin/bash
#ifdef 0 
xxx=$(mktemp -d)
awk 'BEGIN 
 { print "#line 2 \"$0\""; first=1; } 
 { if (first) first=0; else print $0 }' $0 |\
g++ -x c++ -o ${xxx} - && ./${xxx} "$@"
rv=$?
\rm ./${xxx}
exit $rv
#endif
#include <iostream>
int main(int argc,char *argv[]) { 
  std::cout<<"Hello world"<<std::endl; 
}
0

As stated in a previous answer, if you use tcc as your compiler, you can put a shebang #!/usr/bin/tcc -run as the first line of your source file.

However, there is a small problem with that: if you want to compile that same file, gcc will throw an error: invalid preprocessing directive #! (tcc will ignore the shebang and compile just fine).

If you still need to compile with gcc one workaround is to use the tail command to cut off the shebang line from the source file before piping it into gcc:

tail -n+2 helloworld.c | gcc -xc -

Keep in mind that all warnings and/or errors will be off by one line.

You can automate that by creating a bash script that checks whether a file begins with a shebang, something like

if [[ $(head -c2 $1) == '#!' ]]
then
  tail -n+2 $1 | gcc -xc -
else
  gcc $1
fi

and use that to compile your source instead of directly invoking gcc.

Kresimir
  • 777
  • 5
  • 20
0

Just wanted to share, thanks to Pedro's explanation on solutions using the #if 0 trick, I have updated my fork on TCC (Sugar C) so that all examples can be called with shebang, finally, with no errors when looking source on the IDE.

Now, code displays beautifully using clangd in VS Code for project sources. Samples first lines look like:

#if 0
  /usr/local/bin/sugar `basename $0` $@ && exit;
  // above is a shebang hack, so you can run: ./args.c <arg 1> <arg 2> <arg N>
#endif

The original intention of this project always has been to use C as if a scripting language using TCC base under the hood, but with a client that prioritizes ram output over file output (without the of -run directive).

You can check out the project at: https://github.com/antonioprates/sugar

Antonio Prates
  • 322
  • 2
  • 5
0

Hash-bang/shebang examples for a variety of languages, to run them as executable files

You can run even compiled languages as though they were executable scripts.

Here are the necessary hash-bang lines I like to use as the first line at the top of my programs, in order to make this possible:

  1. For C (compiled) (technically: gnu C as I've specified it below):

    ///usr/bin/env ccache gcc -Wall -Wextra -Werror -O3 -std=gnu17 "$0" -o /tmp/a -lm && /tmp/a "$@"; exit
    
  2. For C++ (compiled) (technically: gnu++ as I've specified it below):

    ///usr/bin/env ccache g++ -Wall -Wextra -Werror -O3 -std=gnu++17 "$0" -o /tmp/a -lm && /tmp/a "$@"; exit
    

    For C and C++, ccache helps ensure your compiling is a little more efficient. Install it in Ubuntu with sudo apt update && sudo apt install ccache.

  3. For Go (compiled) (golang)

    ///usr/bin/env go run "$0" "$@"; exit
    

    ...and for more explanations of the lines above, see my other answer here: What's the appropriate Go shebang line?

  4. For Rust (compiled)

    ///usr/bin/env rustc "$0" -o /tmp/a && /tmp/a "$@"; exit
    

    Extra help:

    1. Install Rust: https://www.rust-lang.org/tools/install
      curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
      
    2. The official "Rusty By Example" online book "hello world" example: https://doc.rust-lang.org/stable/rust-by-example/hello.html
  5. For Bash (interpreted)

    #!/usr/bin/env bash
    
  6. For Python (interpreted)

    #!/usr/bin/env python3
    

Prefix with time if you wish

If you'd like to time the compilation time, add bash -c time near the beginning of the hash-bang, as follows. Here is an example for C++ (technically gnu++17 as I've written it):

///usr/bin/env bash -c time ccache g++ -Wall -Wextra -Werror -O3 -std=gnu++17 "$0" -o /tmp/a -lm && /tmp/a "$@"; exit

Now you can mark the files as executables and run them directly!

# make executable
chmod +x hello_world.c      # C
chmod +x hello_world.cpp    # C++
chmod +x hello_world.go     # Go
chmod +x hello_world.rs     # Rust
chmod +x hello_world.sh     # Bash
chmod +x hello_world.py     # Python

# run
cd path/to/dir/containing/these/files
./hello_world.c      # C
./hello_world.cpp    # C++
./hello_world.go     # Go
./hello_world.rs     # Rust
./hello_world.sh     # Bash
./hello_world.py     # Python

References:

  1. You can see and run the above several usages of shebangs for a variety of languages in my eRCaGuy_hello_world repo.
    1. Look for the files named language/hello_world* or language/hello*.
    2. Ex: for C: c/hello_world_extra_basic.c
  2. Shebang starting with //?
  3. [my answer] What's the appropriate Go shebang line?
  4. I've put this on my blog here too: https://gabrielstaples.com/hash-bangs-to-run-compiled-languages-as-executables/
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265