使用 getopt 根据选项标志的顺序更改程序的行为

Behavior of program changing based on the order of option flags using getopt

我正在用 C 语言编写一个 cat 命令克隆,当我更改选项标志的顺序时出现奇怪的行为。

-s 选项标志压缩双倍行距。

-n 选项标记每行从 1 开始编号。

我通过以下方式检查了 运行 我的程序的区别:

$ diff <(./myCat -s spaces.txt) <(cat -s spaces.txt)
no difference

$ diff <(./myCat -n spaces.txt) <(cat -n spaces.txt)
no difference

$ diff <(./myCat -ns spaces.txt) <(cat -ns spaces.txt)
no difference

运行 ./myCat -sn spaces.txt,但是,只压缩文本,不给行编号。 谁能解释这种行为?我认为在这种情况下选项标志的顺序并不重要。

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

int main(int argc, char **argv) {

    FILE *fp;
    const int bufferSize = 4096;
    char buffer[bufferSize];
    int currentFile = 0;

    for (int i = 1; i < argc; i++) {
        if (argv[i][0] != '-') {
            currentFile = i;
            break;
        }
    }

    int bflag = 0, eflag = 0, nflag = 0, sflag = 0;
    int opt;

    while ((opt = getopt(argc, argv, "bens:?")) != -1) {
        switch(opt) {
          case 'b':
            bflag++;
            break;
          case 'e':
            eflag++;
            break;
          case 'n':
            nflag++;
            break;
          case 's':
             sflag++;
             break;
          case ':':
            printf("option needs a value\n");
            exit(1);
          case '?':
            printf("usage: cat [-bens] [file ...]\n");
            exit(1);
        }
    }
    
    while (currentFile < argc) {
        if (currentFile) {
            fp = fopen(argv[currentFile], "rb");
            if (fp == NULL) {
                fprintf(stderr, "%s: %s: No such file or directory",
                        argv[0], argv[currentFile]);
                exit(1);
            }
        }

        int lineNumber = 1;
        int lastLineBlank = 0;

        while (fgets(buffer, bufferSize, (fp == NULL ? stdin : fp))) {

            int length = strlen(buffer);
            buffer[length - 1] = '[=11=]';

            if (sflag) {
                length = strlen(buffer);
                int currentLineBlank = (length <= 1) ? 1 : 0;
                if (lastLineBlank && currentLineBlank) {
                    continue;
                }
                lastLineBlank = currentLineBlank;
            }

            
            if (bflag) {
                length = strlen(buffer);
                if (length >= 1) {
                    char *tmp = strdup(buffer);
                    buffer[0] = '[=11=]';
                    sprintf(buffer, "%*d\t", 6, lineNumber++);
                    strcat(buffer, tmp);
                }
            } else
            if (nflag) {
                char *tmp = strdup(buffer);
                buffer[0] = '[=11=]';
                sprintf(buffer, "%*d\t", 6, lineNumber++);
                strcat(buffer, tmp);
            }

            if (eflag) {
                length = strlen(buffer);
                buffer[length] = '$';
                buffer[length + 1] = '[=11=]';
            }

            fprintf(stdout, "%s\n", buffer);
        }

        fclose(fp);
        currentFile++;
    }

    return 0;
}

这是因为getopt中的optstring:

int getopt(int argc, char *const argv[], const char *optstring);

根据手册页,optstring 的行为是:

optstring is a string containing the legitimate option characters. If such a character is followed by a colon, the option requires an argument, so getopt() places a pointer to the following text in the same argv-element, or the text of the following argv-element, in optarg. Two colons mean an option takes an optional arg; if there is text in the current argv- element (i.e., in the same word as the option name itself, for example, "-oarg"), then it is returned in optarg, otherwise optarg is set to zero.

下面的语句意味着只有 s 需要一个参数:

getopt(argc, argv, "bens:?")

./a.out -n./a.out 上的原始代码段错误)

看来唯一的办法就是使用-ns或在文件名后加上-n。但是 getopt 的 GNU libc 示例非常好 - https://www.gnu.org/software/libc/manual/html_node/Example-of-Getopt.html
我重写了一些代码,以便它处理所有标志,但仍能正确处理文件名。它并不完美,但它是:

// I removed the for loop before this. It is not required here.
// Please remove the for loop if you use this code.
// only the case ':' has been removed here 
while ((opt = getopt(argc, argv, "bens?")) != -1) {
    switch(opt) {
        case 'b':
            bflag++;
            break;
        case 'e':
            eflag++;
            break;
        case 'n':
            nflag++;
            break;
        case 's':
            sflag++;
            break;
        case '?':
            printf("usage: cat [-bens] [file ...]\n");
            exit(1);
    }
}

// optind is set by getopt. 
// It is equal to the position of the immediate argument after the options.
// this works because getopt permutes argv so that all the non-options are at the end
currentFile = optind;

// when no file name is provided
if(currentFile == argc) {
    printf("Need a filename!\n");
    return 1;
}

其余代码保持不变。它适用于这些示例:

  • ./a.out -sn file
  • ./a.out -ns file
  • ./a.out -s file1 file2
  • ./a.out -s file1 -n file2

这可能仍然存在错误。请务必尝试一下,如果有问题请告诉我。