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

c-scripting.md (3032B)


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