TCC can be invoked from scripts just like shell scripts, allowing you to write executable C programs with a shebang line. This enables C to be used for scripting tasks while maintaining the performance and type safety of compiled code.
Basic scripting
Using shebang
Add a shebang line at the start of your C source file to make it executable:
#!/usr/local/bin/tcc -run
#include <stdio.h>
int main()
{
printf("Hello World\n");
return 0;
}
Make the file executable and run it:
chmod +x hello.c
./hello.c
Output:
The shebang path may vary depending on where TCC is installed. Common paths are:
/usr/local/bin/tcc
/usr/bin/tcc
Use which tcc to find the correct path on your system.
Portable shebang
For better portability across different systems, use env:
#!/usr/bin/env tcc -run
#include <stdio.h>
int main()
{
printf("Portable script\n");
return 0;
}
This searches for tcc in the PATH environment variable.
Command-line arguments
Accessing arguments
Script arguments are passed to the main() function:
#!/usr/local/bin/tcc -run
#include <stdio.h>
int main(int argc, char **argv)
{
printf("Script: %s\n", argv[0]);
printf("Arguments: %d\n", argc - 1);
for (int i = 1; i < argc; i++) {
printf(" arg[%d]: %s\n", i, argv[i]);
}
return 0;
}
Run with arguments:
chmod +x script_args.c
./script_args.c hello world 123
Output:
Script: ./script_args.c
Arguments: 3
arg[1]: hello
arg[2]: world
arg[3]: 123
Processing arguments
Here’s a practical example that processes file arguments:
#!/usr/local/bin/tcc -run
#include <stdio.h>
#include <sys/stat.h>
int main(int argc, char **argv)
{
if (argc < 2) {
fprintf(stderr, "Usage: %s <file>...\n", argv[0]);
return 1;
}
for (int i = 1; i < argc; i++) {
struct stat st;
if (stat(argv[i], &st) == 0) {
printf("%s: %ld bytes\n", argv[i], st.st_size);
} else {
fprintf(stderr, "Error: Cannot stat %s\n", argv[i]);
}
}
return 0;
}
TCC options in shebang
Adding compiler options
You can include TCC options in the shebang line:
#!/usr/local/bin/tcc -run -Wall -g
#include <stdio.h>
int main()
{
printf("Script with warnings enabled\n");
return 0;
}
Linking with libraries
Include library flags in the shebang:
#!/usr/local/bin/tcc -run -lm
#include <stdio.h>
#include <math.h>
int main(int argc, char **argv)
{
if (argc < 2) return 1;
double x = atof(argv[1]);
printf("sqrt(%g) = %g\n", x, sqrt(x));
printf("sin(%g) = %g\n", x, sin(x));
return 0;
}
Complex options
For multiple options, quote them as a single argument:
#!/usr/local/bin/tcc -run -L/usr/X11R6/lib -lX11
#include <stdio.h>
#include <X11/Xlib.h>
int main()
{
Display *display = XOpenDisplay(NULL);
if (display) {
printf("X11 display opened successfully\n");
XCloseDisplay(display);
}
return 0;
}
Some shells limit the length and format of shebang lines. Keep option lists reasonable or use configuration files.
Reading from stdin
TCC can execute C code from standard input:
echo 'main(){puts("hello");}' | tcc -run -
Output:
This is useful for quick testing or piping generated code:
cat << 'EOF' | tcc -run -
#include <stdio.h>
int main() {
for (int i = 1; i <= 5; i++)
printf("%d\n", i * i);
return 0;
}
EOF
Output:
Using tcclib
TCC provides tcclib.h, a minimal library header that includes commonly used functions without requiring standard headers:
#!/usr/local/bin/tcc -run
#include <tcclib.h>
int main()
{
printf("Hello from tcclib\n");
return 0;
}
tcclib.h provides:
- Standard I/O functions (
printf, fprintf, puts, etc.)
- Memory functions (
malloc, free, memcpy, etc.)
- String functions (
strlen, strcmp, strcpy, etc.)
- Basic file operations (
fopen, fclose, fread, etc.)
Using tcclib.h instead of standard headers can slightly improve compilation speed for simple scripts.
Practical examples
File processing script
#!/usr/local/bin/tcc -run
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
if (argc < 2) {
fprintf(stderr, "Usage: %s <logfile>\n", argv[0]);
return 1;
}
FILE *f = fopen(argv[1], "r");
if (!f) {
perror(argv[1]);
return 1;
}
char line[1024];
int errors = 0, warnings = 0;
while (fgets(line, sizeof(line), f)) {
if (strstr(line, "ERROR")) errors++;
if (strstr(line, "WARNING")) warnings++;
}
fclose(f);
printf("Log summary:\n");
printf(" Errors: %d\n", errors);
printf(" Warnings: %d\n", warnings);
return 0;
}
Text processing
#!/usr/local/bin/tcc -run
#include <stdio.h>
#include <ctype.h>
int main(int argc, char **argv)
{
FILE *f = argc > 1 ? fopen(argv[1], "r") : stdin;
if (!f) {
perror(argv[1]);
return 1;
}
int lines = 0, words = 0, chars = 0;
int in_word = 0;
int c;
while ((c = fgetc(f)) != EOF) {
chars++;
if (c == '\n') lines++;
if (isspace(c)) {
in_word = 0;
} else if (!in_word) {
in_word = 1;
words++;
}
}
if (argc > 1) fclose(f);
printf("%d lines, %d words, %d chars\n", lines, words, chars);
return 0;
}
Usage:
./word_count.c file.txt
cat file.txt | ./word_count.c
System administration
#!/usr/local/bin/tcc -run
#include <stdio.h>
#include <sys/statvfs.h>
int main(int argc, char **argv)
{
const char *path = argc > 1 ? argv[1] : ".";
struct statvfs stat;
if (statvfs(path, &stat) != 0) {
perror(path);
return 1;
}
unsigned long total = stat.f_blocks * stat.f_frsize;
unsigned long available = stat.f_bavail * stat.f_frsize;
unsigned long used = total - (stat.f_bfree * stat.f_frsize);
int percent = (int)((used * 100) / total);
printf("Filesystem: %s\n", path);
printf("Total: %lu MB\n", total / 1048576);
printf("Used: %lu MB (%d%%)\n", used / 1048576, percent);
printf("Available: %lu MB\n", available / 1048576);
return 0;
}
Compilation overhead
While TCC compiles very quickly, there is still a compilation step each time the script runs:
First run
Subsequent runs
Compiled executable
time ./script.c
# real 0m0.021s (includes compilation)
time ./script.c
# real 0m0.020s (still compiles each time)
tcc -o script script.c
time ./script
# real 0m0.002s (no compilation)
For scripts that run frequently, consider compiling them to avoid the compilation overhead, even though TCC is very fast.
When to use C scripts
C scripting with TCC is ideal for:
- System programming tasks - Direct access to system calls and low-level APIs
- Performance-critical scripts - Faster execution than interpreted languages
- Existing C code reuse - Quickly test or run C code snippets
- Learning and prototyping - Rapid C development without build systems
C scripting may not be ideal for:
- Simple text processing - Shell scripts or awk/sed are simpler
- Complex string manipulation - Higher-level languages have better string support
- Cross-platform portability - Requires TCC on target system
Custom stdin for scripts
Redirect stdin for the running script:
tcc -run -rstdin input.txt script.c
This is useful for testing scripts that read from stdin:
#!/usr/local/bin/tcc -run
#include <stdio.h>
int main()
{
char line[256];
while (fgets(line, sizeof(line), stdin)) {
printf("Got: %s", line);
}
return 0;
}
Test with:
tcc -run -rstdin input.txt read_stdin.c
Debugging scripts
Enable debug info and bounds checking:
#!/usr/local/bin/tcc -run -g -b
#include <stdio.h>
int main()
{
int arr[5] = {1, 2, 3, 4, 5};
// This will be caught with bounds checking:
// printf("%d\n", arr[10]);
printf("Script with debugging enabled\n");
return 0;
}
With -g and -b, you get:
- Clear error messages with file and line numbers
- Bounds checking for arrays and pointers
- Stack traces on errors
Use -bt to enable backtrace support and see the full call stack when errors occur.