之前我们所看到的 main 函数都是不带任何参数的,但就像普通的函数调用一样,在支持 C 语言的环境中,其实可以在程序开始执行的时候将一些参数传递给主函数 main,这些参数我们称为命令行参数。
第五章 指针与数组 >> 5.10 命令行参数
调用主函数 main 时,它有两个参数:第一个参数(习惯上成为 argc,用于参数计数)的值表示运行程序时命令行中参数的个数;第二个参数(称为argv,用于参数向量)是一个指向字符串数组的指针,其中每个字符串对应一个参数。
1. 示例:echo 打印程序
在 linux 的 bash 环境下,有个最简单的例子:echo 程序,它将命令行参数在屏幕上显示出来,其中命令行中各个参数用空格隔开。比如命令
echo hello, world
将在屏幕上打印以下字符串:
hello, world
C语言规定,argv[0] 的值是启动该程序的程序名,因此 argc 的值至少为 1,。(因此如果 argc 等于 1,说明程序名后面没有命令行参数)。
在上面 echo 的例子中,argc的值为 3,argv[0]、argv[1]、argv[2]的值分别是“echo”、“hello,”、”world”,即可选参数的是 argv[1] ~ argv[argc – 1]。另外,ANSI 标准要求 argv[argc]的值必须为一个空指针。参数的图形描述如下:
若把 argv 看成一个字符指针数组,我们可以写出第一个版本的 echo 函数:
#include <stdio.h> /* echo command-line arguments; 1st version */ int main(int argc, char *argv[]) { int i; for(i = 1; i < argc; i++) printf("%s%s", argv[i], (i < argc-1) ? " ": ""); printf("\n"); return 0; }
因为 argv 是一个指向指针数组的指针,所以也可以通过指针而非数组下标的方式处理命令行参数。若把 argv 看成是一个指向 char 类型的指针的指针,我们可以写出第二个版本的 echo 程序:
#include <stdio.h> /* echo command-line arguments; 2nd version */ int main(int argc, char *argv[]) { while(--argc) printf("%s%s", *++argv, (argc > 1) ? " ": ""); printf("\n"); return 0; }
其中 argv 是一个指向参数字符串数组起始位置的指针,自增运算(++argv)将使得它一开始指向 argv[1] 而不是 argv[0]。
另外上面的 printf 语句也可以写成:
printf((argc > 1) ? "%s " : "%s", *++argv);
说明 printf 的格式化参数也可以是表达式。
2. 示例:find 查找字符串程序
在 从头学C(32)函数的基本知识 章节中我们看了一个查找指定字符串的例子,但是改程序将需要查找的字符串在代码中“固定”了,这种程序通用性太低了。这个需要查找的字符串可以以命令行参数的形式传递给主程序,因此我们可以来下一个类似 linux 中 grep 命令的程序。
#include <stdio.h> #include <string.h> #define MAXLINE 1000 int my_getline(char *line, int max); /* find: print lines that match pattern from 1st arg */ int main(int argc, char *argv[]) { char line[MAXLINE]; int found = 0; if(argc !=2) printf("Usage: find pattern\n"); else while(my_getline(line, MAXLINE) > 0) if(strstr(line, argv[1]) != NULL) { printf("%s", line); found++; } return found; } /* my_getline: read a line into s, return length */ int my_getline(char s[], int lim) { int c, i = 0; while(--lim > 0 && (c=getchar()) != EOF && c != '\n') s[i++] = c; if(c == '\n') s[i++] = c; s[i]= '\0'; return i; }
其中的 strstr(s, t) 是标准库函数,它返回一个指针,该指针指向字符串 t 在字符串 s 中第一次出现的位置;如果字符串 t 没有在 s 中出现,则返回 空指针(即 NULL)。
3. 示例:支持选项参数的 find 程序
在 Linux 系统的 bash 环境下,命令的执行可以搭配一些以“-”为开头的选项作为参数。比如 “ ls -l”、“echo -n hello, world”等等。同样我们也可以改进上面的 find 程序,比如使用 -x (代表“除……之外”)表示打印所有与模式不匹配的文本行,用 -n(代表“行号”)表示打印行号,则下面命令:
find -x -n 模式
将打印所有与模式不匹配的行,并在每个打印行的最前面加上行号。
熟悉 Linux 的朋友应该知道,类似 -x 、 -n 这样的可选参数应该被允许以任意次序出现,而且有时还会允许可选参数可以组合使用,比如:
find -nx 模式
改进之后的程序如下:
#include <stdio.h> #include <string.h> #define MAXLINE 1000 int my_getline(char *line, int max); /* find: print lines that match pattern from 1st arg */ int main(int argc, char *argv[]) { char line[MAXLINE]; long lineno = 0; int c, except = 0, number = 0, found = 0; while(--argc >0 && (*++argv)[0] == '-') while(c = *++argv[0]) switch(c){ case 'x': except = 1; break; case 'n': number = 1; break; default: printf("find: illegal option \"%c\"\n", c); argc = 0; found = -1; break; } if(argc != 1) printf("Usage: find -x -n pattern\n"); else while(my_getline(line, MAXLINE) > 0){ lineno++; if((strstr(line, *argv) != NULL) != except) { if(number) printf("%ld:", lineno); printf("%s", line); found++; } } return found; } /* my_getline: read a line into s, return length */ int my_getline(char s[], int lim) { int c, i = 0; while(--lim > 0 && (c=getchar()) != EOF && c != '\n') s[i++] = c; if(c == '\n') s[i++] = c; s[i]= '\0'; return i; }
注意,程序的第 14 行中,*++argv 是一个指向参数字符串的指针,因此 (*++argv)[0] 是该字符串的第一个字符(另一种有效形式是 **++argv)。而由于 [ ] 与操作数的结合优先级比 * 和 ++ 高,所以上述表达式必须使用圆括号,否则编译器会把该表达式认为是 *++(argv[0]) 。
在程序的第 15 行中,我们使用了 *++argv[0] (其实就是上面所说的 *++(argv[0]) 表达式)来对字符串中的单个字符进行遍历,它是对指针 argv[0] 进行自增运算。
实际中一般不会使用比这更复杂的指针表达式。