Back to COMP1511

命令行参数

Command Line Arguments

课程概述 | Course Overview

命令行参数让C程序能够接收外部输入,使程序更加灵活和实用。掌握命令行参数处理是编写专业级C程序的基础技能:

1. 什么是命令行参数?

命令行参数是程序启动时从命令行传递给程序的数据。它们让用户可以在不修改源代码的情况下改变程序的行为。

$ ./my_program file.txt -n 10 --verbose
↑ 这个命令包含了多个参数

参数分解:

  • ./my_program:程序名(argv[0])
  • file.txt:第一个参数(argv[1])
  • -n:选项标志(argv[2])
  • 10:选项值(argv[3])
  • --verbose:长选项(argv[4])

main函数的完整形式:

int main(int argc, char *argv[]) {
    // argc: argument count(参数个数)
    // argv: argument vector(参数向量/数组)
    
    printf("程序接收到 %d 个参数\n", argc);
    
    for (int i = 0; i < argc; i++) {
        printf("argv[%d] = \"%s\"\n", i, argv[i]);
    }
    
    return 0;
}

2. 深入理解 argc 和 argv

2.1 argc(Argument Count)

argc 是一个整数,表示传递给程序的参数总数,包括程序名本身

// 示例:./program hello world
// argc = 3(程序名 + 2个参数)

int main(int argc, char *argv[]) {
    printf("参数总数: %d\n", argc);  // 输出: 参数总数: 3
    
    if (argc < 2) {
        printf("用法: %s <参数1> [参数2] ...\n", argv[0]);
        return 1;  // 错误退出
    }
    
    return 0;
}

2.2 argv(Argument Vector)

argv 是一个字符串数组,每个元素都是一个指向C字符串的指针。

命令:./calculator 15 + 25

argv[0]
"./calculator"
argv[1]
"15"
argv[2]
"+"
argv[3]
"25"

argc = 4

💡 重要提示:

所有命令行参数都是字符串!即使输入数字,也需要使用 atoi()atof() 等函数转换为数值类型。

3. 参数解析与处理

3.1 基本参数遍历

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

int main(int argc, char *argv[]) {
    printf("程序名: %s\n", argv[0]);
    printf("参数个数: %d\n", argc - 1);  // 减去程序名
    
    // 遍历所有参数(跳过程序名)
    for (int i = 1; i < argc; i++) {
        printf("参数 %d: %s\n", i, argv[i]);
    }
    
    return 0;
}

3.2 字符串到数字的转换

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

int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("用法: %s <数字1> <数字2>\n", argv[0]);
        return 1;
    }
    
    // 转换字符串为整数
    int num1 = atoi(argv[1]);
    int num2 = atoi(argv[2]);
    
    printf("%d + %d = %d\n", num1, num2, num1 + num2);
    
    return 0;
}

3.3 参数验证

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

// 检查字符串是否为有效数字
int is_valid_number(const char *str) {
    if (str == NULL || *str == '\0') {
        return 0;  // 空字符串
    }
    
    // 允许负号
    if (*str == '-') {
        str++;
    }
    
    // 检查每个字符是否为数字
    while (*str) {
        if (!isdigit(*str)) {
            return 0;
        }
        str++;
    }
    
    return 1;
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("用法: %s <数字>\n", argv[0]);
        return 1;
    }
    
    if (!is_valid_number(argv[1])) {
        printf("错误: '%s' 不是有效数字\n", argv[1]);
        return 1;
    }
    
    int number = atoi(argv[1]);
    printf("您输入的数字是: %d\n", number);
    printf("它的平方是: %d\n", number * number);
    
    return 0;
}

4. 常见参数模式

4.1 选项标志处理

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

int main(int argc, char *argv[]) {
    int verbose = 0;    // 详细输出标志
    int help = 0;       // 帮助标志
    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], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
            help = 1;
        } else if (argv[i][0] != '-') {
            // 不以 '-' 开头的参数视为文件名
            filename = argv[i];
        } else {
            printf("未知选项: %s\n", argv[i]);
            return 1;
        }
    }
    
    if (help) {
        printf("用法: %s [选项] <文件名>\n", argv[0]);
        printf("选项:\n");
        printf("  -v, --verbose  详细输出\n");
        printf("  -h, --help     显示帮助\n");
        return 0;
    }
    
    if (filename == NULL) {
        printf("错误: 请指定文件名\n");
        return 1;
    }
    
    if (verbose) {
        printf("处理文件: %s\n", filename);
    }
    
    // 处理文件的代码...
    printf("文件 '%s' 处理完成\n", filename);
    
    return 0;
}

4.2 带值的选项

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

int main(int argc, char *argv[]) {
    int count = 1;      // 默认计数
    char *output = NULL; // 输出文件
    
    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-n") == 0) {
            // 下一个参数应该是数字
            if (i + 1 < argc) {
                count = atoi(argv[i + 1]);
                i++;  // 跳过已处理的参数
            } else {
                printf("错误: -n 选项需要一个数值\n");
                return 1;
            }
        } else if (strcmp(argv[i], "-o") == 0) {
            // 下一个参数应该是输出文件名
            if (i + 1 < argc) {
                output = argv[i + 1];
                i++;  // 跳过已处理的参数
            } else {
                printf("错误: -o 选项需要一个文件名\n");
                return 1;
            }
        }
    }
    
    printf("重复次数: %d\n", count);
    if (output) {
        printf("输出文件: %s\n", output);
    } else {
        printf("输出到标准输出\n");
    }
    
    return 0;
}

5. 实际应用示例

5.1 简单计算器

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

int main(int argc, char *argv[]) {
    if (argc != 4) {
        printf("用法: %s <数字1> <操作符> <数字2>\n", argv[0]);
        printf("操作符: +, -, *, /\n");
        return 1;
    }
    
    double num1 = atof(argv[1]);
    char *operator = argv[2];
    double num2 = atof(argv[3]);
    double result;
    
    if (strcmp(operator, "+") == 0) {
        result = num1 + num2;
    } else if (strcmp(operator, "-") == 0) {
        result = num1 - num2;
    } else if (strcmp(operator, "*") == 0) {
        result = num1 * num2;
    } else if (strcmp(operator, "/") == 0) {
        if (num2 == 0) {
            printf("错误: 除数不能为零\n");
            return 1;
        }
        result = num1 / num2;
    } else {
        printf("错误: 不支持的操作符 '%s'\n", operator);
        return 1;
    }
    
    printf("%.2f %s %.2f = %.2f\n", num1, operator, num2, result);
    
    return 0;
}

// 使用示例:
// $ ./calculator 15.5 + 8.3
// 15.50 + 8.30 = 23.80

5.2 文件处理工具

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

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("用法: %s [选项] <文件名1> [文件名2] ...\n", argv[0]);
        printf("选项:\n");
        printf("  -c  统计字符数\n");
        printf("  -l  统计行数\n");
        printf("  -w  统计单词数\n");
        return 1;
    }
    
    int count_chars = 0, count_lines = 0, count_words = 0;
    int file_start = 1;  // 文件名开始的索引
    
    // 解析选项
    for (int i = 1; i < argc && argv[i][0] == '-'; i++) {
        if (strcmp(argv[i], "-c") == 0) {
            count_chars = 1;
        } else if (strcmp(argv[i], "-l") == 0) {
            count_lines = 1;
        } else if (strcmp(argv[i], "-w") == 0) {
            count_words = 1;
        } else {
            printf("未知选项: %s\n", argv[i]);
            return 1;
        }
        file_start++;
    }
    
    // 如果没有指定选项,默认统计所有
    if (!count_chars && !count_lines && !count_words) {
        count_chars = count_lines = count_words = 1;
    }
    
    // 处理每个文件
    for (int i = file_start; i < argc; i++) {
        FILE *file = fopen(argv[i], "r");
        if (file == NULL) {
            printf("错误: 无法打开文件 '%s'\n", argv[i]);
            continue;
        }
        
        int chars = 0, lines = 0, words = 0;
        int in_word = 0;
        int c;
        
        while ((c = fgetc(file)) != EOF) {
            chars++;
            
            if (c == '\n') {
                lines++;
            }
            
            if (c == ' ' || c == '\t' || c == '\n') {
                in_word = 0;
            } else if (!in_word) {
                words++;
                in_word = 1;
            }
        }
        
        fclose(file);
        
        printf("%s: ", argv[i]);
        if (count_lines) printf("%d 行 ", lines);
        if (count_words) printf("%d 单词 ", words);
        if (count_chars) printf("%d 字符", chars);
        printf("\n");
    }
    
    return 0;
}

6. 最佳实践与常见错误

✅ 最佳实践:

  • 始终检查 argc 的值,确保有足够的参数
  • 提供清晰的用法信息和错误消息
  • 验证输入参数的有效性
  • 支持常见的选项约定(-h 帮助,-v 详细)
  • 处理边界情况(如除零、文件不存在等)

⚠️ 常见错误:

  • 忘记检查 argc,导致访问不存在的 argv 元素
  • 直接使用 atoi() 而不验证输入是否为数字
  • 没有处理文件打开失败的情况
  • 选项解析时没有检查是否有足够的参数
  • 错误信息不够清晰,用户不知道如何修正

错误处理模板:

int main(int argc, char *argv[]) {
    // 1. 检查参数个数
    if (argc < 2) {
        fprintf(stderr, "用法: %s <参数>\n", argv[0]);
        return 1;
    }
    
    // 2. 验证参数有效性
    if (!is_valid_input(argv[1])) {
        fprintf(stderr, "错误: 无效输入 '%s'\n", argv[1]);
        return 1;
    }
    
    // 3. 进行实际处理,捕获错误
    if (process_input(argv[1]) != 0) {
        fprintf(stderr, "错误: 处理失败\n");
        return 1;
    }
    
    return 0;  // 成功
}

7. 高级主题

7.1 使用 getopt() 解析选项

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>  // for getopt

int main(int argc, char *argv[]) {
    int opt;
    int verbose = 0;
    char *output_file = NULL;
    int count = 1;
    
    // 解析选项:v 无参数,o: 需要参数,n: 需要参数
    while ((opt = getopt(argc, argv, "vo:n:h")) != -1) {
        switch (opt) {
            case 'v':
                verbose = 1;
                break;
            case 'o':
                output_file = optarg;  // optarg 包含选项的参数
                break;
            case 'n':
                count = atoi(optarg);
                break;
            case 'h':
                printf("用法: %s [-v] [-o 输出文件] [-n 计数] 文件名\n", argv[0]);
                return 0;
            case '?':
                // getopt 会自动打印错误信息
                return 1;
        }
    }
    
    // 处理非选项参数
    for (int i = optind; i < argc; i++) {
        printf("处理文件: %s\n", argv[i]);
    }
    
    return 0;
}

7.2 环境变量结合

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

int main(int argc, char *argv[]) {
    // 从环境变量获取默认值
    char *default_editor = getenv("EDITOR");
    if (default_editor == NULL) {
        default_editor = "vi";  // 默认编辑器
    }
    
    char *editor = default_editor;
    
    // 命令行参数可以覆盖环境变量
    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-e") == 0 && i + 1 < argc) {
            editor = argv[i + 1];
            i++;
        }
    }
    
    printf("使用编辑器: %s\n", editor);
    
    return 0;
}

总结 | Summary

核心概念:

  • argc 和 argv 的含义
  • 参数解析和验证
  • 字符串到数字转换
  • 选项和标志处理
  • 错误处理和用户友好性

实用技能:

  • 构建命令行工具
  • 实现帮助系统
  • 处理多种输入格式
  • 使用标准库函数
  • 遵循UNIX约定

命令行参数是C程序与外界交互的重要接口。掌握了这些技能,你就能编写出真正实用的命令行工具和应用程序。

继续学习