Advanced I/O and Command Line

Command-line arguments are like ordering at a restaurant — you tell the program what you want (argv), the program counts how many items you ordered (argc), and then prepares your order.

Command-Line Arguments

argc and argv

The main function can receive two parameters:

C
int main(int argc, char *argv[])
C
#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("Argument count: %d\n", argc);
    for (int i = 0; i < argc; i++) {
        printf("argv[%d] = %s\n", i, argv[i]);
    }
    return 0;
}

Run:

BASH
./program hello world 123
TEXT
Argument count: 4
argv[0] = ./program
argv[1] = hello
argv[2] = world
argv[3] = 123
💡 Tip: Command-line arguments are all strings! If you need numbers, you must convert them using atoi, atol, or strtol.

Argument Parsing in Practice

In real projects, command-line arguments typically use - prefixes for options:

C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]) {
    int verbose = 0;
    int count = 1;
    const char *filename = NULL;

    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
            verbose = 1;
        } else if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) {
            count = atoi(argv[++i]);
        } else if (argv[i][0] != '-') {
            filename = argv[i];
        } else {
            fprintf(stderr, "Unknown option: %s\n", argv[i]);
            return 1;
        }
    }

    if (filename == NULL) {
        fprintf(stderr, "Usage: %s [-v] [-n count] filename\n", argv[0]);
        return 1;
    }

    printf("File: %s, Count: %d, Verbose: %s\n",
           filename, count, verbose ? "on" : "off");
    return 0;
}
⚠️ Note: Manual argument parsing is error-prone. Complex projects can use the getopt function, which handles both short and long option combinations.

Environment Variables

getenv

C
char *getenv(const char *name);

Returns the value of the environment variable, or NULL if it doesn't exist.

C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    const char *path = getenv("PATH");
    if (path != NULL) {
        printf("PATH = %s\n", path);
    }

    const char *home = getenv("HOME");
    if (home != NULL) {
        printf("HOME = %s\n", home);
    }
    return 0;
}

The extern Variable environ

C defines a global variable environ that points to an array of all environment variable strings:

C
#include <stdio.h>

extern char **environ;

int main(void) {
    char **env = environ;
    while (*env) {
        printf("%s\n", *env);
        env++;
    }
    return 0;
}
💡 Tip: Environment variables are commonly used to configure program behavior, such as database connection strings and log levels. This is much more flexible than hardcoding values into source code.

errno Error Handling

The errno Mechanism

When C standard library functions encounter errors, they typically don't crash directly. Instead, they set the global variable errno and return an error value:

C
#include <errno.h>

errno is not automatically cleared to zero after a successful library function call! You must only check it after a function call has failed.

perror / strerror

C
void perror(const char *s);
char *strerror(int errnum);

perror outputs s: error message to stderr. strerror returns the text description corresponding to the error code.

C
#include <stdio.h>
#include <errno.h>
#include <string.h>

int main(void) {
    FILE *fp = fopen("nonexistent_file.txt", "r");
    if (fp == NULL) {
        perror("fopen");
        printf("Error code: %d, Error message: %s\n", errno, strerror(errno));
        return 1;
    }
    fclose(fp);
    return 0;
}
TEXT
fopen: No such file or directory
Error code: 2, Error message: No such file or directory

Example

Complete error handling pattern:

C
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s filename\n", argv[0]);
        return 1;
    }

    FILE *fp = fopen(argv[1], "r");
    if (fp == NULL) {
        fprintf(stderr, "Cannot open '%s': %s\n", argv[1], strerror(errno));
        return 1;
    }

    int ch;
    while ((ch = fgetc(fp)) != EOF) {
        putchar(ch);
    }

    if (ferror(fp)) {
        fprintf(stderr, "Read error occurred: %s\n", strerror(errno));
        fclose(fp);
        return 1;
    }

    fclose(fp);
    return 0;
}
▶ Try it Yourself
⚠️ Note: ferror checks whether a stream has experienced a read/write error, which is different from feof (which checks for end of file).

Standard Streams

C automatically opens three streams when a program starts:

Stream Name Default Device File Descriptor
stdin Standard input Keyboard 0
stdout Standard output Screen 1
stderr Standard error Screen 2

The difference between stdout and stderr: stdout is buffered, stderr is unbuffered. When redirecting, they do not affect each other.

C
#include <stdio.h>

int main(void) {
    fprintf(stdout, "This is normal output\n");
    fprintf(stderr, "This is error output\n");
    return 0;
}
BASH
./program > output.txt

After running this way, "normal output" goes to the file, while "error output" still appears on the screen.

Redirection Principles

At the command line:

BASH
./program 2>&1 all.log

This command merges stderr into stdout and writes both to all.log.

Temporary Files

tmpfile

C
FILE *tmpfile(void);

Creates a temporary file opened in "wb+" mode. The file is automatically deleted when closed or when the program ends.

C
#include <stdio.h>

int main(void) {
    FILE *tmp = tmpfile();
    if (tmp == NULL) {
        perror("tmpfile");
        return 1;
    }

    fprintf(tmp, "Temporary data %d\n", 42);
    rewind(tmp);

    char buf[64];
    while (fgets(buf, sizeof(buf), tmp) != NULL) {
        printf("%s", buf);
    }

    fclose(tmp);
    return 0;
}
TEXT
Temporary data 42

tmpnam

C
char *tmpnam(char *s);

Generates a temporary file name that doesn't conflict with existing files. However, there's a race condition — between generating the name and creating the file, another program could create a file with the same name.

⚠️ Note: Prefer tmpfile — it atomically creates and opens the file, making it safer. tmpnam is not safe in multi-threaded environments.

Example

Using a temporary file to process intermediate data:

C
#include <stdio.h>
#include <string.h>

int main(void) {
    FILE *tmp = tmpfile();
    if (tmp == NULL) {
        perror("tmpfile");
        return 1;
    }

    char *lines[] = {"banana", "apple", "orange", "grape"};
    int n = 4;

    for (int i = 0; i < n; i++) {
        fprintf(tmp, "%s\n", lines[i]);
    }

    rewind(tmp);
    printf("Before sorting:\n");
    char buf[64];
    while (fgets(buf, sizeof(buf), tmp) != NULL) {
        buf[strcspn(buf, "\n")] = '\0';
        printf("  %s\n", buf);
    }

    rewind(tmp);
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - 1 - i; j++) {
            char a[64], b[64];
            long pos = ftell(tmp);
            fgets(a, sizeof(a), tmp);
            fgets(b, sizeof(b), tmp);
            a[strcspn(a, "\n")] = '\0';
            b[strcspn(b, "\n")] = '\0';

            if (strcmp(a, b) > 0) {
                fseek(tmp, pos, SEEK_SET);
                fprintf(tmp, "%s\n%s\n", b, a);
            } else {
                fseek(tmp, pos, SEEK_SET);
                fprintf(tmp, "%s\n%s\n", a, b);
            }
            fseek(tmp, pos + strlen(a) + strlen(b) + 2, SEEK_SET);
        }
        rewind(tmp);
    }

    rewind(tmp);
    printf("After sorting:\n");
    while (fgets(buf, sizeof(buf), tmp) != NULL) {
        buf[strcspn(buf, "\n")] = '\0';
        printf("  %s\n", buf);
    }

    fclose(tmp);
    return 0;
}
▶ Try it Yourself
TEXT
Before sorting:
  banana
  apple
  orange
  grape
After sorting:
  apple
  grape
  orange
  banana

Advanced Formatted Output

snprintf

C
int snprintf(char *str, size_t size, const char *format, ...);

Compared to sprintf, snprintf adds a size parameter to limit the write length, preventing buffer overflow.

C
#include <stdio.h>

int main(void) {
    char buf[10];
    int n = snprintf(buf, sizeof(buf), "Hello, %s!", "World");
    printf("buf = \"%s\", required length = %d\n", buf, n);
    return 0;
}
TEXT
buf = "Hello, Wo", required length = 13
💡 Tip: The return value of snprintf is the length that the formatted output should have, not the actual number of bytes written. If the return value is greater than size-1, the output was truncated.

❓ FAQ

Q What is the order of arguments in argv?
A argv[0] is the program name itself, and argv[1] onward are the user-supplied arguments, in the same order as on the command line.
Q Why does perror output to stderr instead of stdout?
A Because error messages should not be lost when stdout is redirected to a file. stderr is unbuffered and independent from stdout, ensuring error messages are always visible.
Q When is errno cleared to zero?
A It is never automatically cleared. You must manually set errno = 0 before calling a function that might fail, and then check it after the function returns an error value.

Q: Where does tmpfile create its file? A: The location is system-dependent, typically a temporary directory (such as /tmp). You don't need to care about the path because tmpfile returns a FILE, and the file disappears automatically when the program ends.*

📖 Summary

📝 Exercises

  1. Write a command-line program that accepts -c to count characters, -l to count lines, and -w to count words in a file
  2. Write a program that uses tmpfile to reverse the contents of a file (write everything to a temporary file, then read it back in reverse order)
  3. Write a program that demonstrates the difference between stdout and stderr redirection, using > and 2> to capture each type of output separately
100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏