-
-
Save hadrianw/5cd275d5838814d813adc12978dce9be to your computer and use it in GitHub Desktop.
#if 0 | |
set -e; [ "$0" -nt "$0.bin" ] && | |
gcc -Wall -Wextra -pedantic -std=c99 "$0" -o "$0.bin" | |
exec "$0.bin" "$@" | |
#endif | |
#include <stdio.h> | |
int | |
main(int argc, char *argv[]) { | |
int ch = getchar(); | |
printf("Hello %c world!\n", ch); | |
return 0; | |
} |
To avoid rebuilding every time:
-{ echo "#line 1 \"$0\""; cat "$0"; } |
-gcc -xc -Wall -Wextra -pedantic -std=c99 - -o "$0.bin"
+printf '%%.bin: %%\n\tgcc -xc -Wall -Wextra -pedantic -std=c99 $^ -o $@' | make "$0.bin" -s -f -
Thanks for suggestion. However I updated it to check if file is newer in shell if it needs rebuilding without calling make.
What if I wanted to erase $0.bin
after execution? I don't mind rebuilding it anew on every re-run.
@stefanos82 I think there is no way to make it elegant, but something like this would work:
#if 0
set -e
gcc -Wall -Wextra -pedantic -std=c99 "$0" -o "$0.bin"
trap 'rm -f "$0.bin"' EXIT
"$0.bin" "$@"
exit $?
#endif
The problem is, that we need to leave a shell process running for the clean up. Because of that we can't use exec and we must exit ourselves. But thanks to set -e
we could just as well give exit 0
there, as in case of errors we would still get a correct exit code from our program.
This way is a bit less nice, also because the PID of our process is different from the PID of the command. We could think about putting the binary in /tmp and use first mktemp
so it could also work from a read-only place like read-only mount or a different user. But I don't like it, because the name (argv[0]
) will be cryptic and we can't deduce from the binary were the sources are and some programs may want to have access to some additional files. A solution to that would be exec -a
which gives ability to set argv[0]
explicitly. However /bin/sh
is often dash
and not bash
(like on Debian) so it is not supported.
Other way that makes it possible to use exec
that I thought about is more hacky, but maybe there is a way to make it less racy:
#if 0
set -e
gcc -Wall -Wextra -pedantic -std=c99 "$0" -o "$0.bin"
{ sleep 1; rm -f "$0.bin"; } &
exec "$0.bin" "$@"
#endif
Shell runs a background process that will wait just a bit and then remove the binary. If the parent process will get to exec
before this one second will run out everything is good. Because the kernel keeps a file as long as there is a file handle opened to it. In this case a running program keeps a handle. It may not be accessible from the file system, but everything works as intended. I'm not sure what could be done to avoid this racy one second sleep. On the one hand it can be too long, because in less than a second the program could already be done and we wait with removal longer than neccessary. On the other hand it can be too short in a busy system or a slow file system. It would be better for it to wait only as long as neccessary. I wonder if there is a way in shell to have an opened file descriptor with close-on-exec flag set and then the clean-up subprocess would attempt to read from it - then it should break on exec and we would be in the clear.
Or rather open a file from parent with exec 3<> /tmp/tmpfile
then in the clean-up subprocess instead of sleep
put read <&3
, but I did not test it out too much. Probably should work, but then you should replace /tmp/tmpfile with proper mktemp
. But all this will look bad.
Not much tested:
#if 0
set -e
gcc -Wall -Wextra -pedantic -std=c99 "$0" -o "$0.bin"
tmp=$(mktemp)
exec 3<> "$tmp"
{ read <&3; rm -f "$0.bin" "$tmp"; } &
exec "$0.bin" "$@"
#endif
Yeah, it makes sense as you describe the whole behavior.
Basically I thought of TCC's and Dlang's -run
flags; what I know for sure is that DMD would generate an executable at /tmp
, run it, and then delete it immediately.
Of course as you have said yourself, the workarounds you have just shared (thank you by the way) are quite hacky and a bit verbose to say the least, therefore I should stick with your original preprocessor technique.
Thank you for your thorough explanation, I was reminded how script behavior works.
If your sh supports
exec -a
you could do a bit nicer: