aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastiano Tronto <sebastiano@tronto.net>2024-09-20 14:26:43 +0200
committerSebastiano Tronto <sebastiano@tronto.net>2024-09-20 14:26:43 +0200
commitf0b6a7f1c683446651b72ef9a968b90f548a42b2 (patch)
tree458f7bb3d1275cd444f067e88bc65ae019b4cca3
parent480851093e36799c4ccfaddf68d34819175377f7 (diff)
downloadsebastiano.tronto.net-f0b6a7f1c683446651b72ef9a968b90f548a42b2.tar.gz
sebastiano.tronto.net-f0b6a7f1c683446651b72ef9a968b90f548a42b2.zip
new blog post
-rw-r--r--src/blog/2024-09-20-c-scripting/c-scripting.md95
-rwxr-xr-xsrc/blog/2024-09-20-c-scripting/script.c15
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
new file mode 100644
index 0000000..de70812
--- /dev/null
+++ b/src/blog/2024-09-20-c-scripting/c-scripting.md
@@ -0,0 +1,95 @@
1# Shell scripting in C
2
3Recently, one of my [shell scripts](https://git.tronto.net/scripts)
4grew too large. It is probably time to rewrite it in a proper
5language. If possible I would like to still keep it as a script,
6so I don't have to add a compilation step to the installation process
7of my scripts. Sure, I could use a scripting language like Python
8or Perl... but what if I wanted to rewrite this script in C?
9
10To be more precise, I want to write a single C file `script.c`,
11make it executable with `chmod +x`, move it to somewhere in my
12`$PATH` and then be able to run it simply by typing `script.c` in
13my shell. It turns out that this is actually possible. Let's see
14how *right now* so we don't have the time to wonder if it is a wise
15thing to do (it does not sound like it).
16
17## The trick
18
19The single thing that makes it all possible is that lines starting
20with `#` are preprocessor directives for C and comments for a UNIX
21shell. We can then use the `#if` directive for conditional compilation
22to "hide" a shell script in our souce file:
23
24```
25#if 0 /* Always false, skipped by the C compiler */
26
27[Your shell script here]
28
29exit 0 # Shell script terminates
30#endif
31
32[Your C source code here]
33```
34
35And now we just have to come up with a shell script that compiles
36itself - that it, that runs a C compiler on its own source file.
37For this we can run the `realpath` command on the shell parameter
38`$0` to obtain the full path to the source file. Then we can compile
39this file and save the executable to a temporary file, which we
40finally run. The "C Script Hello World" looks like this:
41
42```
43#if 0
44
45bin="$(mktemp)"
46cc -o "$bin" "$(realpath $0)"
47"$bin"
48
49exit 0
50#endif
51
52#include <stdio.h>
53
54int main() {
55 printf("Hello, world!\n");
56 return 0;
57}
58```
59
60If you want to try this for yourself, you can download
61[script.c](./script.c) instead of copy-pasting.
62
63## Caveats
64
65There are few caveats with this approach. First of all, you are
66running a C compiler every time you launch this "script". This is
67obviously less efficient than running a normal shell script.
68
69Secondly, the `realpath` command I used above is not standard. I
70thought it was in POSIX, but only the C library function with the
71same name is, not the command itself. However, it is present in
72most Linux distros since around 2012 and OpenBSD since version 7.1
73- surprisingly, after I started using each of these operating
74systems! The command is also included in FreeBSD (since 4.3) and
75MacOS (since 13), and there are probably workarounds to make the
76same concept work without it.
77
78## Credits
79
80I did not come up with this trick - I read about it in at least
81three separate occasions from different places. Had I saved any of
82the links I would add them here.
83
84## For completeness, a python version
85
86In case you did not know, making this work with an interpreted
87language is much simpler. You just need to use a
88[shebang](https://en.wikipedia.org/wiki/Shebang_(Unix)) to make
89the shell use the correct interpreter:
90
91```
92#!/usr/bin/env python3
93
94print("Hello, World!")
95```
diff --git a/src/blog/2024-09-20-c-scripting/script.c b/src/blog/2024-09-20-c-scripting/script.c
new file mode 100755
index 0000000..de57959
--- /dev/null
+++ b/src/blog/2024-09-20-c-scripting/script.c
@@ -0,0 +1,15 @@
1#if 0
2
3bin="$(mktemp)"
4cc -o "$bin" "$(realpath $0)"
5"$bin"
6
7exit 0
8#endif
9
10#include <stdio.h>
11
12int main() {
13 printf("Hello, world!\n");
14 return 0;
15}

Generated with cgit - Back to sebastiano.tronto.net