【问题标题】:How to get filename from command line如何从命令行获取文件名
【发布时间】:2019-03-31 02:41:35
【问题描述】:

我知道以前在 Stack Overflow 上有人问过这种性质的问题,但即使在阅读了一些帖子 (1)(2) 之后,我也无法取得成功。

我正在编写一个 C 函数,它将读取指定为命令行参数的文件名。但我也有空间放置文件名之前的 一个 可选参数。

示例执行(这三个调用相互独立):

./my_program -a foo.txt  // Standalone example #1
./my_program -b foo.txt  // Standalone example #2
./my_program foo.txt  // Standalone example #3

我的代码:

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

  int aflag = 0;
  int bflag = 0;
  int cflag = 0;
  int option;
  char *filename;

  while ((option = getopt(argc, argv, "abc:")) != -1) {
    switch (option) {
    case 'a':
      aflag = 1;
      break;
    case 'b':
      bflag = 1;
      break;
    case 'c':
      cflag = 1;
      break;
    default:
      aflag = 1;  // If no flags are set, use "a"
      break;
    }
  }

  if (argc == 2) {
    filename = argv[1];
  } else if (argc == 3) {
    filename = argv[2];
  }

  printf("Flags: aflag = %d, bflag = %d, cflag = %d\n", aflag, bflag, cflag);
  printf("Got filename = %s\n", filename);

确实适用于带有一个可选参数的情况。

但是,我正在阅读有关 [optind](3) 的信息,并且想知道它的正确用法是什么,以便我可以获取文件名。我似乎无法让它工作,我不知道使用这样的if 语句是否是好的风格。

例如,目前此代码仅限于 一个 可选参数。但是,如果我后来决定添加第二个参数怎么办?那么我上面的代码就不行了,因为文件名所在的argv索引会发生变化。

有没有办法——大概使用getindgetopt——总是将最后一个参数作为文件名,无论我在它之前指定了多少(可选)参数?

【问题讨论】:

  • 处理完所有选项参数后,if (argc > optindex) /* the next arguments will be your filename */
  • @DavidC.Rankin 抱歉,我不太明白您所说的“下一个参数将是您的文件名”是什么意思。你的意思是filename = argv[optind + 1] 在任何情况下?
  • 假设您的选项列表中有 "-f:" 作为文件名选项。您可以在命令行上传递"-f filename",并且不会保留未处理的其他参数。现在假设您只提供"filename" 作为命令行上的参数。将保留 1 个附加参数,您可以将其用作文件名。
  • 强烈建议所有文件名参数都以-f 参数开头,以便对getopt() 的调用可以像任何其他命令行参数一样简单地访问filename

标签: c getopt


【解决方案1】:

从您链接的页面:

如果没有更多的选项字符,getopt() 返回 -1。然后 optind 是第一个 argv 元素的 argv 中不是选项的索引。

所以代替

if (argc == 2) {
    filename = argv[1];
  } else if (argc == 3) {
    filename = argv[2];
  }

你只是想要

filename = argv[optind];

请注意,如果在选项之后没有指定任何参数(例如,如果您的程序被简单地调用为./my_program -a,那么这会将filename 设置为NULL,您应该准备好相应地处理这个问题。您可以如果您愿意,也可以明确检测这种情况:

if (optind < argc) {
    filename = argv[optind];
} else {
    fprintf(stderr, "Usage: %s -a|-b|-c filename\n", argv[0]);
    exit(2);
}

【讨论】:

  • 如果我写filename = argv[optind],那么当我包含一个可选参数时,filename 变成(null)
  • 我不确定您所说的“包含可选参数”是什么意思。你能举一个你正在谈论的命令行的例子吗?
  • @WaterGuy:如果你的意思是你运行了./my_program -c foo,那么这是意料之中的:当你在getopt 字符串中包含c: 时,foo 被解释为@987654334 的参数@ 选项,当getopt 返回'c' 时,optarg 将被设置为指向它。在这种情况下foo 是选项的一部分,并且在选项解析完成后没有任何参数。因此,在这种情况下,optind 设置为等于argc,而argv[argc] 是一个空指针,正如我在答案中所解释的那样。您的printf 显然打印为字符串"(null)",但正如我所提到的,您应该真正处理这种情况。
  • 通过可选参数,我的意思是标志(例如-a)是可选的添加。如果标志不存在,它会自动默认为-a。在后一种情况下,如果我不包含-a,我会得到(null) 作为filename。所以我想这是上面代码中的else 案例。然后我将如何获取文件名?最好只使用argv[1]
  • @WaterGuy 我仍然没有关注你,而且我不确定我们是否始终如一地使用“参数”和“标志”这两个术语。我再重复一遍:你能举一个你正在谈论的完整命令行的具体例子吗?在我看来,您的意思是 ./my_program foo,但我不确定。
【解决方案2】:

当您与getopt 交朋友时,请了解getopt 基本上会解析您的命令行、匹配选项以及需要值的选项的任何参数。所有其他非选项参数都被重新排序,因此它们出现在参数列表的末尾。当您像通常检查一样循环时,例如while ((opt = getopt (argc, argv, "f:ohv")) != -1),任何不是选项且不是选项所需值的命令行参数将保持从argv[optind] 开始。因此,当您的参数处理循环完成时,您检查if (optind &lt; argc) 以确定您是否有其他可用的命令行参数未在您的getopt 循环中处理。

让我们举一个处理文件名的相当完整的例子,要么在"-f" 选项之后给出,要么仅作为处理所有选项后保留的第一个非参数选项(或者如果没有其他选项,我们将阅读stdin -- 但请注意,在这种情况下,您不能有其他选项,否则第一个选项将被视为要读取的文件名)

处理处理参数的最简单/最方便的方法之一就是简单地声明一个您将其初始化为全零的选项数组。然后在处理选项时使用opts 数组,其中每个元素要么保存argv 中相应选项的索引,要么保存一个标志(例如,如果设置了选项,则设置为1),或者从转换(例如,如果您让"-n:" 输入一些数字,然后使用包含"-n 4" 的命令行,您可以转换并存储实际值4"-n" 选项关联的数组索引处(而不是argv 索引,您稍后必须将其转换为数值))。

processopts() 函数的目的是循环使用getopt(),并将任何选项完全转换为可用值,以供程序的其余部分使用。通过使用选项数组,这可以很容易地作为参数传递给处理所有选项的函数。通过将选项数组的类型设置为long,您可以使用原生宽度和strtol 转换,并且能够处理正值和负值。

让我们看一个使用processopts() 函数的示例。在main() 或任何您将调用processopts() 的地方,您只需声明一个数组,其中每个元素将对应于您将处理的某个选项,并在处理该选项后保留一个有意义的值,例如

#define NOPTS   8   /* max options for sizing opts array */
...
int main (int argc, char **argv) {

    long opts[NOPTS] = {0};  /* initialize opitons array all zero */
    ...
    int optindex = processopts (argc, argv, opts);  /* process all options */

所以上面你已经声明了你的 opts 数组并将它与 argc,argv 一起传递给你的 processopts() 函数。然后,您的 processopts() 函数将执行以下操作:

/** process command line options with getopt.
 *  values are made available through the 'opts' array.
 *  'optind' is returned for further command line processing.
 */
int processopts (int argc, char **argv, long *opts)
{
    int opt;

    /* set any default values in *opts array here */

    while ((opt = getopt (argc, argv, "f:ohv")) != -1) {  /* getopt loop */
        switch (opt) {
            case 'f':       /* filename */
                opts[0] = optind - 1;
                break;
            case 'o':       /* some generic option 'o' */
                opts[1] = 1;
                break;
            case 'h':       /* help */
                help (EXIT_SUCCESS);
            case 'v':       /* show version information */
                printf ("%s, version %s\n", PACKAGE, VERSION);
                exit (EXIT_SUCCESS);
            default :       /* ? */
                fprintf (stderr, "\nerror: invalid or missing option.\n");
                help (EXIT_FAILURE);
        }
    }
    /* set argv index for filename if arguments remain */
    if (!opts[0] && argc > optind) opts[0] = optind++;

    return optind;  /* return next argument index */
}

注意上面如果给"-f filename"选项opts[0]设置为下一个参数(文件名)的索引,然后在最后进行测试以确定是否检查附加跳过用作文件名的参数,因为 opts[0] 不再是 0。但如果opts[0] 未设置,则第一个非选项参数的index 将存储在opts[0] 中。无论文件名是在"-f" 之后获取的,还是作为第一个非选项参数读取的,您都可以调用fopen (argv[opts[0]], "r")main() 中打开文件。

另请注意 optind 被返回,允许您确定是否有其他(或额外)参数未在您的 getopt 循环中处理,因此您可以检查 if (optind &lt; argc) 返回在main() 中处理你认为合适的额外参数。

将它放在一个简短的(用于 getopt)示例中,您可以尝试使用类似以下的方法在 "-f" 或其他任何没有 "-f" 的地方传递文件名,只要它是第一个非选项参数仍然存在,例如

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

#define PACKAGE "getopt_example"
#define VERSION "0.01"

#define NOPTS   8   /* max options for sizing opts array */
#define MAXC 1024   /* max characters for buffer */

int processopts (int argc, char **argv, long *opts);
void help (int xcode);
size_t rmcrlf (char *s);

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

    long opts[NOPTS] = {0};  /* initialize opitons array all zero */
    char buf[MAXC] = "";
    size_t idx = 0;
    int optindex = processopts (argc, argv, opts);

    /* use filename provided as following "-f" option or provided as
     * 1st non-option argument (stdin by default)
     */
    FILE *fp = opts[0] ? fopen (argv[opts[0]], "r") : stdin;
    if (!fp) {  /* validate file open for reading */
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }
    /* indicate whether the option '-o' was set */
    printf ("\nthe option '-o' %s set.\n\n", opts[1] ? "is" : "is not");

    printf (" line : len - contents\n\n");
    while (fgets (buf, MAXC, fp)) { /* read ouput length/lines from file */
        size_t l = rmcrlf (buf);    /* get line length, trim line ending */
        printf (" %4zu : %3zu - %s\n", idx++, l, buf);
    }

    if (fp != stdin)        /* close file if not stdin */
        fclose (fp);

    if (optindex < argc)    /* check whether additional options remain */
        printf ("\nwarning: %d options unprocessed.\n\n", argc - optindex);

    for (int i = optindex; i < argc; i++)   /* output unprocessed options */
        printf (" %s\n", argv[i]);

    return 0;
}

/** process command line options with getopt.
 *  values are made available through the 'opts' array.
 *  'optind' is returned for further command line processing.
 */
int processopts (int argc, char **argv, long *opts)
{
    int opt;

    /* set any default values in *opts array here */

    while ((opt = getopt (argc, argv, "f:ohv")) != -1) {
        switch (opt) {
            case 'f':       /* filename */
                opts[0] = optind - 1;
                break;
            case 'o':       /* some generic option 'o' */
                opts[1] = 1;
                break;
            case 'h':       /* help */
                help (EXIT_SUCCESS);
            case 'v':       /* show version information */
                printf ("%s, version %s\n", PACKAGE, VERSION);
                exit (EXIT_SUCCESS);
            default :       /* ? */
                fprintf (stderr, "\nerror: invalid or missing option.\n");
                help (EXIT_FAILURE);
        }
    }
    /* set argv index for filename if arguments remain */
    if (!opts[0] && argc > optind) opts[0] = optind++;

    return optind;  /* return next argument index */
}

/** display help */
void help (int xcode)
{
    xcode = xcode ? xcode : 0;

    printf ("\n %s, version %s\n\n"
            "  usage:  %s [-hv -f file (stdin)] [file]\n\n"
            "  Reads each line from file, and writes line, length and contents\n"
            "  to stdout.\n\n"
            "    Options:\n\n"
            "      -f file    specifies filename to read.\n"
            "                 (note: file can be specified with or without -f option)\n"
            "      -o         generic option for example.\n"
            "      -h         display this help.\n"
            "      -v         display version information.\n\n",
            PACKAGE, VERSION, PACKAGE);

    exit (xcode);
}

/** remove newline or carriage-return from 's'.
 *  returns new length on success, -1 of 's' is NULL.
 */
size_t rmcrlf (char *s)
{
    size_t len;

    if (!s) return 0;                       /* validate s not NULL */

    s[(len = strcspn (s, "\r\n"))] = 0;     /* nul-terminate saving len */

    return len;     /* return len */
}

(程序会告诉您是否设置了"-o" 选项"is""is not",然后只需读取在命令行参数中找到的文件名(或stdin,如果没有提供文件名或附加参数)并吐出行索引 (0 - N-1)、行的长度,最后是行本身,然后是 getoptprocessopts() 函数中未处理的任何其他参数。

示例命令行可以是:

$ ./bin/getopt_min -f dat/captnjack.txt extra1 extra2

(读取文件dat/captnjack.txt 并显示有两个额外的参数未处理)

$ ./bin/getopt_min dat/captnjack.txt -o extra1 extra2

(相同)

$ ./bin/getopt_min -o <dat/captnjack.txt

(在stdin上读取文件)

最后,"-h""-v" 选项只会显示帮助或版本信息。

检查一下,如果您有任何问题,请告诉我。消化getopt 需要一些时间,这很正常,只需打开手册页并通过几个示例进行操作即可。

【讨论】:

  • 感谢您的详细回复。这将非常有帮助。
猜你喜欢
  • 2021-09-15
  • 1970-01-01
  • 1970-01-01
  • 2010-09-16
  • 1970-01-01
  • 2011-10-25
  • 1970-01-01
  • 2017-11-26
  • 2016-09-22
相关资源
最近更新 更多