sebastiano.tronto.net

Source files and build scripts for my personal website
git clone https://git.tronto.net/sebastiano.tronto.net
Download | Log | Files | Refs | README

commit f0b6a7f1c683446651b72ef9a968b90f548a42b2
parent 480851093e36799c4ccfaddf68d34819175377f7
Author: Sebastiano Tronto <sebastiano@tronto.net>
Date:   Fri, 20 Sep 2024 14:26:43 +0200

new blog post

Diffstat:
Asrc/blog/2024-09-20-c-scripting/c-scripting.md | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/blog/2024-09-20-c-scripting/script.c | 15+++++++++++++++
2 files changed, 110 insertions(+), 0 deletions(-)

diff --git a/src/blog/2024-09-20-c-scripting/c-scripting.md b/src/blog/2024-09-20-c-scripting/c-scripting.md @@ -0,0 +1,95 @@ +# Shell scripting in C + +Recently, one of my [shell scripts](https://git.tronto.net/scripts) +grew too large. It is probably time to rewrite it in a proper +language. If possible I would like to still keep it as a script, +so I don't have to add a compilation step to the installation process +of my scripts. Sure, I could use a scripting language like Python +or Perl... but what if I wanted to rewrite this script in C? + +To be more precise, I want to write a single C file `script.c`, +make it executable with `chmod +x`, move it to somewhere in my +`$PATH` and then be able to run it simply by typing `script.c` in +my shell. It turns out that this is actually possible. Let's see +how *right now* so we don't have the time to wonder if it is a wise +thing to do (it does not sound like it). + +## The trick + +The single thing that makes it all possible is that lines starting +with `#` are preprocessor directives for C and comments for a UNIX +shell. We can then use the `#if` directive for conditional compilation +to "hide" a shell script in our souce file: + +``` +#if 0 /* Always false, skipped by the C compiler */ + +[Your shell script here] + +exit 0 # Shell script terminates +#endif + +[Your C source code here] +``` + +And now we just have to come up with a shell script that compiles +itself - that it, that runs a C compiler on its own source file. +For this we can run the `realpath` command on the shell parameter +`$0` to obtain the full path to the source file. Then we can compile +this file and save the executable to a temporary file, which we +finally run. The "C Script Hello World" looks like this: + +``` +#if 0 + +bin="$(mktemp)" +cc -o "$bin" "$(realpath $0)" +"$bin" + +exit 0 +#endif + +#include <stdio.h> + +int main() { + printf("Hello, world!\n"); + return 0; +} +``` + +If you want to try this for yourself, you can download +[script.c](./script.c) instead of copy-pasting. + +## Caveats + +There are few caveats with this approach. First of all, you are +running a C compiler every time you launch this "script". This is +obviously less efficient than running a normal shell script. + +Secondly, the `realpath` command I used above is not standard. I +thought it was in POSIX, but only the C library function with the +same name is, not the command itself. However, it is present in +most Linux distros since around 2012 and OpenBSD since version 7.1 +- surprisingly, after I started using each of these operating +systems! The command is also included in FreeBSD (since 4.3) and +MacOS (since 13), and there are probably workarounds to make the +same concept work without it. + +## Credits + +I did not come up with this trick - I read about it in at least +three separate occasions from different places. Had I saved any of +the links I would add them here. + +## For completeness, a python version + +In case you did not know, making this work with an interpreted +language is much simpler. You just need to use a +[shebang](https://en.wikipedia.org/wiki/Shebang_(Unix)) to make +the shell use the correct interpreter: + +``` +#!/usr/bin/env python3 + +print("Hello, World!") +``` diff --git a/src/blog/2024-09-20-c-scripting/script.c b/src/blog/2024-09-20-c-scripting/script.c @@ -0,0 +1,15 @@ +#if 0 + +bin="$(mktemp)" +cc -o "$bin" "$(realpath $0)" +"$bin" + +exit 0 +#endif + +#include <stdio.h> + +int main() { + printf("Hello, world!\n"); + return 0; +}