如何从命令行获取文件名

时间:2019-03-31 02:41:36

标签: c getopt

我知道之前已经在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-不管我之前指定了多少个(可选)参数,总是将最后一个参数作为文件名?

2 个答案:

答案 0 :(得分:2)

在您链接的页面上:

  

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

因此代替

REM Clear the screen
REM ------------------------------------------------------
cls


date /T >> C:\\myTemp\devTracker.log 
time /T >> C:\\myTemp\devTracker.log 


ECHO Starting Microsoft Visual Studio...

您只是想要

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

请注意,如果在选项之后未指定任何参数(例如,如果仅以filename = argv[optind]; 的形式调用程序,则会将./my_program -a设置为filename,因此您应该准备好相应地进行处理。如果愿意,您还可以显式检测这种情况:

NULL

答案 1 :(得分:1)

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

让我们以一个相当完整的示例来处理文件名,该文件名要么在"-f"选项之后给出,要么作为处理完所有选项后剩下的第一个非自变量选项给出(否则,我们将读取stdin没有其他选项可用-但请注意,在这种情况下,您将没有其他选项,否则第一个将作为要读取的文件名)

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

processopts()函数的用途是与getopt()循环,并将所有选项完全转换为可用值,以供程序的其余部分使用。通过使用选项数组,可以轻松地将其作为参数传递给函数以处理所有选项。通过设置选项数组long的类型,您可以进行strtolprocessopts()转换的本机宽度,并且可以处理正值和负值。

因此,让我们来看一个使用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,数组,并将其与argvprocessopts()一起传递给了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]的{​​{1}}选项设置为下一个参数(文件名)的索引,然后在最后进行测试以确定是否检查是否将其他参数用作文件名,因为opts[0]不再是0。但是,如果未设置opts[0],则第一个非选项参数的 index 将存储在opts[0]中。无论文件名是在"-f"之后获取还是作为第一个非选项参数读取,您都可以调用fopen (argv[opts[0]], "r")main()中打开文件。

还请注意:返回了optind,使您可以确定是否有getopt循环中未处理的其他(或额外)参数,因此您可以检查if (optind < argc)返回main(),并根据需要处理其他参数。

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

#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),行的长度,最后是行本身,其后是getopt或{{ 1}}功能。

示例命令行可能是:

processopts()

(读取文件$ ./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"会花费一些时间,这很正常,只需打开手册页并通过几个示例进行操作即可。