从头学C(52)命令行参数

0

之前我们所看到的 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]的值必须为一个空指针。参数的图形描述如下:

C语言_命令行参数_echo

若把 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] 进行自增运算。

实际中一般不会使用比这更复杂的指针表达式。

Leave A Reply