-
-
Save jdarpinian/1952a58b823222627cc1a8b83a7aa4e2 to your computer and use it in GitHub Desktop.
///$(which true);FLAGS="-g -Wall -Wextra --std=c17 -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) $FLAGS "$THIS_FILE" -o "$OUT_FILE" || exit $?;exec bash -c "exec -a \"$0\" \"$OUT_FILE\" $([ $# -eq 0 ] || printf ' "%s"' "$@")" | |
#include <stdio.h> | |
int main() { | |
printf("Hello world!\n"); | |
return 0; | |
} |
///$(which true);FLAGS="-g -Wall -Wextra --std=c++23 -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++) $FLAGS "$THIS_FILE" -o "$OUT_FILE" || exit $?;exec bash -c "exec -a \"$0\" \"$OUT_FILE\" $([ $# -eq 0 ] || printf ' "%s"' "$@")" | |
#include <iostream> | |
int main() { | |
std::cerr << "Hello, world!" << std::endl; | |
return 0; | |
} |
#!/bin/bash | |
///$(which true);FLAGS="-g -Wall -Wextra --std=c17 -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" || tail -c +12 "$0" | $(which clang || which gcc) $FLAGS -xc - -o "$OUT_FILE" || exit $?;exec -a "$0" "$OUT_FILE" "$@" | |
#include <stdio.h> | |
int main() { | |
printf("Hello world!\n"); | |
return 0; | |
} |
#!/bin/bash | |
///$(which true);FLAGS="-g -Wall -Wextra --std=c++23 -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" || tail -c +12 "$0" | $(which clang++ || which g++ || which cpp) $FLAGS -xc++ - -o "$OUT_FILE" || exit $?;exec -a "$0" "$OUT_FILE" "$@" | |
#include <iostream> | |
int main() { | |
std::cerr << "Hello, world!" << std::endl; | |
return 0; | |
} |
Here is my approach, also allowing for argv to be populated arbitrarily:
https://gist.github.com/CRTified/de7da2976a3b4e7f3f66403797655e2e
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;
}
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;
}
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;
}
This is so dirty I would never tolerate it in a source tree of mine, but its still a nice hack.
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.
And to add something constructive to this thread: #!/usr/bin/tcc -run
Thank you for this <3
These won't work on Mac because true
is not in /bin, but replace it with ///$(which true)
and they do.
gcc
does not like the option -fsanitize=address,undefined
on my machine; I had to delete that part, in order to make it work.
what's apply scenarios?
nice hack, and fun, but just fun
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;
}
//usr/bin/env clang++ $0 -o ${o=`mktemp`} && exec $o $*
#include <stdio.h>
int main() {
printf("Hello World!\n");
return 0;
}
Due to an issue described here there is a warning printed on macOS that can be remedied using
///$(which true);export MallocNanoZone=0;COMPILER_OPTIONS="-g -Wall -Wextra --std=c++17 -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;
}
Thanks, I've incorporated some of your suggestions!
Forget having a build system. Just chmod +x your source code!
More concise versions of this trick exist, but they lack some of the features of this one. It caches the executable in /tmp so that it's only rebuilt if the source changes. It transparently forwards argv to the C program including argv[0]. It works when executed from bash, zsh, dash, and other shells (though bash should be installed).
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 will run when called from a shell. However, it can't be executed with the execve system call. (It can be executed with execlp or execvp.)
The shebang version isn't valid C, so the file can only be compiled by directly executing it (which strips the shebang for the compiler). On the plus side, it can be executed with execve.
These should all work on most Unix varieties including most Linux distros, macOS, and various BSDs. Windows users can use WSL.
Invoking gdb on the script itself will fail because it is not a binary, but you can easily debug it by starting with a shell process first like so:
You can set breakpoints and everything should work as normal.