C / C ++命令解析器和标记器

时间:2016-09-15 06:52:37

标签: c++ c

我在弄清楚这究竟是什么问题时遇到了很多麻烦。谁可以给我解释一下这个?我是Unix的新手,我在使用终端和编译代码时遇到了麻烦。

问题是要求我设计和实现一个应该解析用户命令的C / C ++程序。

  1. 一个带参数和选项的命令         例如,ls -la ./cs3000             命令:ls             选项:-la             参数:./ cs3000

3 个答案:

答案 0 :(得分:4)

命令基本上是一个字符串。一般来说,它可以分为两部分 - 命令' name和命令&{39} {。}}。

示例:

arguments

用于列出目录的内容:

ls

上面的user@computer:~$ ls Documents Pictures Videos ... 在用户的ls文件夹中执行。这里将列出要列出的文件夹的参数隐式添加到命令中。我们可以显式传递一些参数:

home

我已明确告诉user@computer:~$ ls Picture image1.jpg image2.jpg ... 我希望看到哪个文件夹的内容。我们可以使用另一个参数ls来列出每个文件和文件夹的详细信息,例如访问权限,大小等。:

l

哦,大小看起来很奇怪(user@computer:~$ ls Pictures -rw-r--r-- 1 user user 215867 Oct 12 2014 image1.jpg -rw-r--r-- 1 user user 268800 Jul 31 2014 image2.jpg ... 215867)。让我们为人类友好的输出添加268800标志:

h

有些命令允许将它们的参数组合在一起(在上面的例子中我们也可以写user@computer:~$ ls -l -h Pictures -rw-r--r-- 1 user user 211K Oct 12 2014 image1.jpg -rw-r--r-- 1 user user 263K Jul 31 2014 image2.jpg ... 并且我们得到相同的输出),使用short(通常只有一个字母但有时更多;缩写)或长名称(如果ls -lh我们有ls-a列出所有文件,包括--all作为--all的长名称的隐藏文件有些命令参数的顺序非常重要,但也有一些命令参数的顺序根本不重要

例如,如果我使用-als -lh并不重要,但在ls -hl(移动/重命名文件)的情况下,您的最后2个灵活性较低参数是mv

为了掌握命令及其参数,您可以使用mv [OPTIONS] SOURCE DESTINATION(例如:man)或man ls(例如:info)。

在包括C / C ++在内的许多语言中,您都可以解析用户附加到可执行文件调用(命令)的命令行参数。还有许多可用于此任务的库,因为它的核心实际上并不容易正确地执行它,同时提供大量的参数及其变体:

  • info ls
  • getopt
  • argp_parse
  • ...

每个C / C ++应用程序都有所谓的入口点,它基本上是代码启动的地方 - gflags函数:

main

无论你是否使用了一个图书馆(就像我上面提到过的那个;但你的情况显然不允许这样做;))或者你自己做int main (int argc, char *argv[]) { // When you launch your application the first line of code that is ran is this one - entry point // Some code here return 0; // Exit code of the application - exit point } 函数就可以了两个论点:

  • main - 代表参数的数字
  • argc - 指向字符串数组的指针(您还可以看到基本相同但更难以使用的argv)。

注意: char** argv实际上还有第三个参数main,它允许将环境变量传递给您的命令,但这是一个更高级的事情,我真的不喜欢'认为你的情况需要它。

命令行参数的处理由两部分组成:

  1. 令牌化 - 这是每个参数获得含义的部分。它是将你的参数列表分解为有意义的元素(标记)的过程。在char *envp[]的情况下,ls -l不仅是一个有效的字符,而且还是一个令牌,因为它代表了一个完整有效的参数。
  2. 以下是如何输出参数数量和(未经检查的有效性)字符的示例,这些字符可能是或可能不是参数:

    l
    1. 解析 - 获取令牌(参数及其值)后,您需要检查命令是否支持这些。例如:

      #include <iostream>
      using std::cout;
      using std::endl;
      
      int main (int argc, char *argv[]) {
          cout << "Arguments' count=%d" << argc << endl;
      
          // First argument is ALWAYS the command itself
          cout << "Command: " << argv[0] << endl;
      
          // For additional arguments we start from argv[1] and continue (if any)
          for (int i = 1; i < argc; i++) {
              cout << "arg[" << i << "]: " << argv[i] << endl;
          }
      
          cout << endl;
          return 0;
      }
      

      将返回

      user@computer:~$ ls -y
      

      这是因为解析失败了。为什么?因为分别是ls: invalid option -- 'y' Try 'ls --help' for more information. (和y;请注意-y---等不是必需的,而且无论你是否解析参数想要那些东西;在Unix / Linux系统中,这是一种约定,但你没有绑定它)是:命令的未知参数。

    2. 对于每个参数(如果成功识别),您将在应用程序中触发某种更改。例如,您可以使用ls来检查某个参数是否有效,以及在执行其余代码时更改您希望该参数更改的内容。你可以采用旧的C风格或C ++风格:

      if-else

      我实际上喜欢(不使用库时)将* `if (strcmp(argv[1], "x") == 0) { ... }` - compare the pointer value * `if (std::string(argv[1]) == "x") { ... }` - convert to string and then compare 转换为argv这样的字符串:

      std::vector

      std::vector<std::string> args(argv, argv+argc); for (size_t i = 1; i < args.size(); ++i) { if (args[i] == "x") { // Handle x } else if (args[i] == "y") { // Handle y } // ... } 部分只是一种更简单的C ++ - 处理字符串数组的方法,因为std::vector<std::string> args(argv, argv+argc);是一个C风格的字符串(char *是这样的字符串数组)它可以很容易地转换为char *argv[]的C ++字符串。然后我们可以通过给出std::string的起始地址然后指向它的最后一个地址argv(我们将argv + argc字符串数添加到基地址,将所有转换后的字符串添加到向量中argc,它基本上指向我们数组的最后一个地址。)

      在上面的argv循环中,您可以看到我检查(使用简单的for)如果某个参数可用,如果是,则相应地处理它。 提醒:通过使用这样的循环,参数的顺序并不重要。正如我在开始时提到的,一些命令实际上对其部分或全部参数有严格的命令。您可以通过手动调用每个if-else的内容(或args,如果您使用的是初始argv而不是矢量解决方案)以不同的方式处理此问题:

      char* argv[]

      这确保了在// No for loop! if (args[1] == "x") { // Handle x } else if (args[2] == "y") { // Handle y } // ... 位置只会预期1等等。这样做的问题是你可以通过索引超出界限射击自己,这样你就拥有了确保您的索引保持在x

      设置的范围内
      argc

      上面的示例确保您拥有索引if (argc > 1 && argc <= 3) { if (args[1] == "x") { // Handle x } else if (args[2] == "y") { // Handle y } } 1但不会超出的内容。

      最后但并非最不重要的是,每个论点的处理都完全取决于你。您可以使用在检测到某个参数时设置的布尔标志(例如:2,然后在您的代码中根据if (args[i] == "x") { xFound = true; }及其值执行某些操作),如果参数为a,则为数字类型数字OR由数字和参数的名称组成(例如:bool xFound有一个参数mycommand -x=4,您可以另外解析为-x=4x最后一个4的值等等。基于手头的任务,你可能会发疯并为你的命令行参数添加一些疯狂的复杂性。

      希望这会有所帮助。如果有什么不清楚或者您需要更多示例,请告诉我。

答案 1 :(得分:1)

根据我的评论和rbaleksandar的回答,传递给C中任何程序的参数都是字符串值。您将获得参数计数argc),它为您提供从当前正在运行的程序名称开始的参数索引从零开始(总是argv[0])。这使得1 - argc之间的所有参数都作为用户为程序提供的参数。每个都是一个包含在参数向量中的字符串(它是一个指向字符串数组的指针,你会看到它被写为char *argv[],或者等价于函数参数char **argv)每个字符串argv[1]argv[argc-1]都可供您使用,您只需要测试哪个参数是哪个。

这将允许您分离,并使它们可用作命令cmd),选项opt),最后参数arg)到您的cmd

现在值得注意的是,shell的规则(bash等)适用于传递给程序的参数,分词路径名在代码获取参数之前应用变量扩展。因此,您必须考虑在任何参数周围是否需要单个或更多共同双引号,以防止原本应用的shell分裂(例如{{1} }会导致ls -al my file.txt用户提供的代码参数,而4ls -al "my file.txt"会导致您期望的ls -al my\ file.txt

把所有这些放在一起,你可以做一些类似于下面的解析。 (你也可以自由地使用3而不是嵌套的switch等来做这件事......)

if

示例使用/输出

如果你运行代码,你会发现它为参数提供了分离,并提供了单独的指针以方便它们的使用:

#include <stdio.h>

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

    char *cmd = NULL,   /* here, since you are using the arguments  */
         *opt = NULL,   /* themselves, you can simply use a pointer */
         *arg = NULL;   /* or the argument itself without a copy    */

    /* looping using the acutal argument index & vector */
    for (int i = 1; i < argc; i++) {
        if (*argv[i] != '-') {      /* checking if the 1st char is - */
            if (!cmd)               /* cmd is currently NULL, and    */
                cmd = argv[i];      /* no '-' it's going to be cmd   */
            else                    /* otherwise, cmd has value, so  */
                arg = argv[i];       /* the value will be opt        */
        }
        else                /* here the value has a leading '-', so  */
            opt = argv[i];  /* it will be the option */
    }

    printf ("\n cmd : %s\n opt : %s\n arg : %s\n\n",
            cmd, opt, arg);

    return 0;
}

(重要的是要注意,如果您的任务是构建一个命令字符串,您需要将多个值复制为$ ./bin/parse_cmd ls -la ./cs3000 cmd : ls opt : -la arg : ./cs3000 opt,那么您就不能再使用指针,并且需要创建存储,或者通过简单地声明数组而不是指针开始,或者您可以根据需要动态分配存储,例如argmalloc和/或{{1然后你就可以使用存储来复制和连接其中的值。)

如果这是你的挑战,那么在这里的所有答案之间,你应该掌握如何处理你的问题。如果你必须更进一步,实际让你的程序执行calloc realloccmd,那么你会想看看opt产生一个半独立的您运行的流程会执行与argfork类似的cmd optarg。祝你好运,如果还有其他问题,请发表评论。

答案 2 :(得分:0)

您的C / C ++程序始终具有主要功能。它看起来像是:

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

这里argc是一些命令行参数,已经传递给你的程序,而argv是一个带有这些参数的字符串数组。因此,调用程序进程将命令行分隔为in-to参数(这不是单行,就像在windows中一样)。

现在你需要对它们进行排序:

  • 命令名始终是第一个参数(索引0)。
  • 选项只是指定程序应如何工作的特殊参数。按照惯例,他们从 - 标志开始。通常 - 一个字母选项和 - 更长的时间。所以在你的任务&#34;选项&#34;是所有的论据,都是从 - 而不是第0个。
  • 参数。只是所有其他不是程序名称或选项的参数。