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 ```