tcc-run<(...)printf '%s\n''#include <'{stdio,limits}'.h>''int main() { printf("%u\n", INT_MAX); }'How to know what’s the value of the constant INT_MAX in
the C header <limits.h>? There are many ways.
However, here’s how I like to do it: by running a script in C. Here it
is:
$ tcc -run <(printf '%s\n' '#include <'{stdio,limits}'.h>' 'int main() { printf("%u\n", INT_MAX); }')
2147483647
Now that you’ve seen this, let’s take this command line apart, going from the left to the right.
tccThis part is easy, TCC (the “Tiny C Compiler”) is a C compiler.
-runThis flag allows TCC to run C programs without creating intermediate files, which is useful for a throwaway script like ours.
The script can accept other arguments as well. Here’s an example
where arguments 23 and 34 are passed to the C
program. with the output being the string 57:
$ tcc -run <(printf '%s\n' '#include <'{stdio,stdlib}'.h>' 'int main(int argc, char **argv) { printf("%d\n", atoi(argv[1]) + atoi(argv[2])); }') 23 34
57
<(...)This feature of Bash is called process substitution. In this case, it allows output of the command inside the parenthesis to be available using a filename.
Here’s an example:
$ echo <(echo hello)
/dev/fd/63
$ cat <(echo hello)
hello
As we can see from echo <(echo hello), from the
perspective of the receiving command process substitution expression
gets replaced by the string /dev/fd/N where N
is the number of the descriptor Bash opened to capture the output of
echo hello.
What’s special about /dev/fd/N? This path provides us
access to the file descriptor N. In the second example we
can see that the file /dev/fd/N can be read by the
receiving process to get the standard output of echo hello.
cat <(echo hello) prints hello.
printf '%s\n'What’s printf here? Turns
out that printf is not only a name of a function in C, but
also of a utility. It allows us to do formatted output
(printf allows to print formatted). The first
argument is the format string (%s\n instructs to print each
argument on a single line), the rest are the arguments to be formatted.
Here’s an example:
$ printf 'the argument: %s\n' hello '' world
the argument: hello
the argument:
the argument: world
Each of the three arguments ("hello", "",
and "world") is printed on a separate line and prefixed by
the string "the argument". Even the empty string.
Why do we need this here? The answer is the C preprocessor.
As we’ll see later, in our C code there are two different C headers
which we need: stdio.h for the printf C
function and limits.h for the definition of the macro
INT_MAX. This requires two include directives, and there
can’t be multiple directives on one line. Which is why we use
printf here to specify multiple lines in a single command
line, one per argument.
'#include <'{stdio,limits}'.h>'What’s interesting here is the {stdio,limits} part. What
is it? A yet another feature of Bash called brace
expansion. It allows us to add prefixes and suffixes to
multiple strings at once. Here’s an example:
$ printf '%s\n' dir/{a,b,c}.c
dir/a.c
dir/b.c
dir/c.c
As we can see, strings a, b, and
c are prefixed by dir/ and suffixed by
.c.
What does '#include <'{stdio,limits}'.h>' do then?
That’s right, it prefixes strings stdio and
limits with the string #include < and
suffixes them with the string .h>. To verify this, let’s
the results of brace expansion on a separate line with
printf:
$ printf '%s\n' '#include <'{stdio,limits.}'.h>'
#include <stdio.h>
#include <limits.h>
'int main() { printf("%u\n", INT_MAX); }'This is the meat of the program we want to run, put into single
quotes to be presented as a single string to printf.
Together with the two include directives this forms a complete C program.
This is it.
To summarize, let’s see the final program by replacing
tcc -run with cat:
$ cat <(printf '%s\n' '#include <'{stdio,limits}'.h>' 'int main(void) { printf("%d\n", INT_MAX); }')
#include <stdio.h>
#include <limits.h>
int main(void) { printf("%d\n", INT_MAX); }
This will print the value INT_MAX. This is exactly what
we wanted.
For Plan 9
fans out there, here’s a way to do the same in the rc
shell.
rc has analogues for process substitution and brace
expansion, so the command line will be quite similar:
; tcc -run <{printf '%s\n' '#include <'^(stdio limits)^'.h>' 'int main(void) { printf("%d\n", INT_MAX); }'}
2147483647
For programs that don’t require reading from the standard input (like the one we have), the command line can be simpler, requiring no process substitution:
$ printf '%s\n' '#include <'{stdio,limits}'.h>' 'int main() { printf("%u\n", INT_MAX); }' | tcc -run -
2147483647
This makes tcc read the program from the standard input.
tcc -run /dev/stdin would also work.