命令基本上是一个字符串。一般来说,它可以分为两部分——命令的name和命令的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
...
某些命令允许组合它们的参数(在上述情况下,我们不妨写ls -lh,我们会得到相同的输出),使用短名称(通常是单个字母,但有时更多;缩写)或长名称(在ls 的情况下,我们有-a 或--all 用于列出所有文件,包括隐藏文件,其中--all 是-a 的长名称)等等。有些命令的顺序参数非常重要,但也有其他的参数的顺序根本不重要。
例如,我使用ls -lh 或ls -hl 都没有关系,但是在mv(移动/重命名文件)的情况下,您的最后两个参数mv [OPTIONS] SOURCE DESTINATION 的灵活性较低。
为了掌握命令及其参数,您可以使用man(例如:man ls)或info(例如:info ls)。
在包括 C/C++ 在内的许多语言中,您都有一种解析用户附加到可执行文件(命令)调用的命令行参数的方法。还有许多可用于此任务的库,因为在其核心中,正确地完成它实际上并不容易,同时提供了大量的参数及其种类:
getopt
argp_parse
gflags
- ...
每个 C/C++ 应用程序都有所谓的入口点,基本上就是您的代码开始的地方 - 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 实际上还有第三个参数 char *envp[] 允许将环境变量传递给您的命令,但这是一个更高级的东西,我真的不认为它是必需的你的情况。
命令行参数的处理由两部分组成:
-
标记化 - 这是每个参数获得含义的部分。它是将参数列表分解为有意义的元素(令牌)的过程。在
ls -l 的情况下,l 不仅是一个有效字符,而且本身也是一个标记,因为它代表一个完整、有效的参数。
这里是一个示例,如何输出参数的数量和(未经检查的有效性)实际上可能是也可能不是参数的字符:
#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 系统,这是一种约定,但您没有绑定到它)是 ls 命令的未知参数。
对于每个参数(如果成功识别为这样),您会触发应用程序中的某种更改。例如,您可以使用if-else 来检查某个参数是否有效以及它的作用,然后更改您希望该参数在执行其余代码时更改的任何内容。你可以使用旧的 C 风格或 C++ 风格:
* `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
}
// ...
}
std::vector<std::string> args(argv, argv+argc); 部分只是处理字符串数组的一种更简单的 C++ 方式,因为 char * 是 C 风格的字符串(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; } 以及稍后在您的代码中根据bool xFound 及其值执行某些操作)、数字类型(如果参数是数字或由以下内容组成)数字以及参数的名称(例如:mycommand -x=4 有一个参数 -x=4,您还可以将其解析为 x 和 4 最后一个是 x 的值)等。根据您手头的任务可能会发疯并为您的命令行参数增加疯狂的复杂性。
希望这会有所帮助。如果有不清楚的地方或者您需要更多示例,请告诉我。