Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Add one line to your C/C++ source to make it executable.
///bin/true;COMPILER_OPTIONS="-g -Wall -Wextra --std=c99 -O1 -fsanitize=address,undefined";THIS_FILE="$(cd "$(dirname "$0")"; pwd -P)/$(basename "$0")";OUT_FILE="/tmp/build-cache/$THIS_FILE";mkdir -p "$(dirname "$OUT_FILE")";test "$THIS_FILE" -ot "$OUT_FILE" || $(which clang || which gcc) $COMPILER_OPTIONS -xc "$THIS_FILE" -o "$OUT_FILE" || exit;exec "$OUT_FILE" "$@"
#include <stdio.h>
int main() {
printf("Hello world!\n");
return 0;
}
///bin/true;COMPILER_OPTIONS="-g -Wall -Wextra --std=c++14 -O1 -fsanitize=address,undefined";THIS_FILE="$(cd "$(dirname "$0")"; pwd -P)/$(basename "$0")";OUT_FILE="/tmp/build-cache/$THIS_FILE";mkdir -p "$(dirname "$OUT_FILE")";test "$THIS_FILE" -ot "$OUT_FILE" || $(which clang++ || which g++) -xc++ $COMPILER_OPTIONS "$THIS_FILE" -o "$OUT_FILE" || exit;exec "$OUT_FILE" "$@"
#include <iostream>
int main() {
std::cerr << "Hello, world!" << std::endl;
return 0;
}
#!/bin/bash
///bin/true;COMPILER_OPTIONS="-g -Wall -Wextra --std=c99 -O1 -fsanitize=address,undefined";THIS_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd -P)/$(basename "${BASH_SOURCE[0]}")";OUT_FILE="/tmp/build-cache/$THIS_FILE";mkdir -p "$(dirname "$OUT_FILE")";test "$THIS_FILE" -ot "$OUT_FILE" || tail -c +12 "$0" | $(which clang || which gcc) -xc $COMPILER_OPTIONS -o "$OUT_FILE" - || exit $?;exec -a "$0" "$OUT_FILE" "$@"
#include <stdio.h>
int main() {
printf("Hello world!\n");
return 0;
}
#!/bin/bash
///bin/true;COMPILER_OPTIONS="-g -Wall -Wextra --std=c++14 -O1 -fsanitize=address,undefined";THIS_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd -P)/$(basename "${BASH_SOURCE[0]}")";OUT_FILE="/tmp/build-cache/$THIS_FILE";mkdir -p "$(dirname "$OUT_FILE")";test "$THIS_FILE" -ot "$OUT_FILE" || tail -c +12 "$0" | $(which clang++ || which g++) -xc++ $COMPILER_OPTIONS -o "$OUT_FILE" - || exit $?;exec -a "$0" "$OUT_FILE" "$@"
#include <iostream>
int main() {
std::cerr << "Hello, world!" << std::endl;
return 0;
}
@jdarpinian

This comment has been minimized.

Copy link
Owner Author

commented Feb 6, 2018

Forget having a build system. Just chmod +x your source code!

There are 4 varieties here: C vs C++, and shebang vs no shebang.

The shebang/no shebang versions have different minor advantages and disadvantages. The no shebang version is a true one-liner, and the file remains completely valid C that can be compiled in the normal way in addition to the direct execution way. It also doesn't require bash to be installed; any shell should work. However, it can't be executed with execve, or with the shell's "source" built-in command, and it doesn't forward argv[0] to the C binary.

The shebang version isn't valid C, so the file can only be compiled by directly executing it (which strips the shebang). It also requires bash explicitly. On the plus side, it can be executed with execve like a real binary, and with the shell's "source" built-in command. Plus, it correctly sets argv[0] when calling the C binary.

All versions accept an unlimited number of arguments correctly, cache the compiled binary, and work with both GCC and Clang. They should work on most Unix varieties including most Linux distros, macOS, and various BSDs. Windows users are out of luck (although you could try using Cywgin, MinGW, or WSL).

@dolmen

This comment has been minimized.

@Amarandus

This comment has been minimized.

Copy link

commented Feb 6, 2018

Here is my approach, also allowing for argv to be populated arbitrarily:
https://gist.github.com/Amarandus/de7da2976a3b4e7f3f66403797655e2e

@mohd-akram

This comment has been minimized.

Copy link

commented Feb 6, 2018

Alternatively:

//usr/bin/cc -o ${o=`mktemp`} "$0" && exec -a "$0" "$o" "$@"
#include <stdio.h>

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

This comment has been minimized.

Copy link

commented Feb 6, 2018

Fun stuff!

Here's yet another way:

///usr/bin/env make ${0%%.cpp} CXXFLAGS="-g -Wall -Wextra -std=c++14 -O1" && exec ./${0%%.cpp}
#include <iostream>

int main() {
  std::cerr << "Hello, world!" << std::endl;
  return 0;
}
@graph

This comment has been minimized.

Copy link

commented Feb 6, 2018

long ago I found this somewhere. Compiles only if there is a change.

#/*
output=/tmp/`md5sum main.cpp | awk '{ print $1 }'`
# echo $output
# check if source is newer than compiled file
if [[ "$0" -nt "$output" ]]; then
	g++ $0 -o $output
fi
# exec runs command in current process
exec $output
# will not reach here cause exec has started $output in this process
exit 1
#*/

#include <iostream>

int main(){
	std::cout << "hello world\n";
	return 0;
}
@duaneking

This comment has been minimized.

Copy link

commented Feb 6, 2018

This is so dirty I would never tolerate it in a source tree of mine, but its still a nice hack.

@yath

This comment has been minimized.

Copy link

commented Feb 6, 2018

I would be surprised if any of these that don’t have #!/bin/bash there would run without a shell but, say, under /usr/bin/env. Most likely the shell is attempting an execve() call and then falls back to parsing the file as a shell script.

Edit: I just realized that this behaviour of running a file as a shell script if it’s not otherwise executable is a special behaviour of execlp() and execvp() - not the shell. From POSIX.1-2008:

There are two distinct ways in which the contents of the process image file may cause the execution to fail, distinguished by the setting of errno to either [ENOEXEC] or [EINVAL] (see the ERRORS section). In the cases where the other members of the exec family of functions would fail and set errno to [ENOEXEC], the execlp() and execvp() functions shall execute a command interpreter and the environment of the executed command shall be as if the process invoked the sh utility using execl() as follows:

execl(<shell path>, arg0, file, arg1, ..., (char *)0);

where is an unspecified pathname for the sh utility, file is the process image file, and for execvp(), where arg0, arg1, and so on correspond to the values passed to execvp() in argv[0], argv[1], and so on.

Still, you probably don’t want to rely on your caller calling the right exec*() variant for the shebang-less files to work (which, incidentally, both env and perl do). :)

I learned something. Thanks.

@yath

This comment has been minimized.

Copy link

commented Feb 6, 2018

And to add something constructive to this thread: #!/usr/bin/tcc -run

@redcodefinal

This comment has been minimized.

Copy link

commented Feb 7, 2018

Thank you for this <3

@Jartza

This comment has been minimized.

Copy link

commented Feb 7, 2018

These won't work on Mac because true is not in /bin, but replace it with ///$(which true) and they do.

@bontchev

This comment has been minimized.

Copy link

commented Feb 7, 2018

gcc does not like the option -fsanitize=address,undefined on my machine; I had to delete that part, in order to make it work.

@kevien

This comment has been minimized.

Copy link

commented Feb 8, 2018

what's apply scenarios?

@compilelife

This comment has been minimized.

Copy link

commented Feb 8, 2018

nice hack, and fun, but just fun

@cedriczirtacic

This comment has been minimized.

Copy link

commented Feb 8, 2018

And Perl?

#ifndef C
$_=$0,s/\.c$//,print`gcc -DC -o $_ $0 && ./$_; rm -f $_`;
__END__
#endif
#include <stdio.h>

int main() {
    puts("Hello World!");
    return 0;
}
@torarnv

This comment has been minimized.

Copy link

commented Aug 8, 2018

//usr/bin/env clang++ $0 -o ${o=`mktemp`} && exec $o $*
#include <stdio.h>
int main() {
    printf("Hello World!\n");
    return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.