我在弄清楚这究竟是什么问题时遇到了很多麻烦。谁可以给我解释一下这个?我是Unix的新手,我在使用终端和编译代码时遇到了麻烦。
问题是要求我设计和实现一个应该解析用户命令的C / C ++程序。
答案 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
的长名称的隐藏文件有些命令参数的顺序非常重要,但也有一些命令参数的顺序根本不重要。
例如,如果我使用-a
或ls -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
,它允许将环境变量传递给您的命令,但这是一个更高级的事情,我真的不喜欢'认为你的情况需要它。
命令行参数的处理由两部分组成:
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
来检查某个参数是否有效,以及在执行其余代码时更改您希望该参数更改的内容。你可以采用旧的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=4
和x
最后一个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
用户提供的代码参数,而4
或ls -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
,那么您就不能再使用指针,并且需要创建存储,或者通过简单地声明数组而不是指针开始,或者您可以根据需要动态分配存储,例如arg
,malloc
和/或{{1然后你就可以使用存储来复制和连接其中的值。)
如果这是你的挑战,那么在这里的所有答案之间,你应该掌握如何处理你的问题。如果你必须更进一步,实际让你的程序执行calloc
realloc
和cmd
,那么你会想看看opt
产生一个半独立的您运行的流程会执行与arg
或fork
类似的cmd
opt
和arg
。祝你好运,如果还有其他问题,请发表评论。
答案 2 :(得分:0)
您的C / C ++程序始终具有主要功能。它看起来像是:
int main(int argc, char**argv) {
...
}
这里argc是一些命令行参数,已经传递给你的程序,而argv是一个带有这些参数的字符串数组。因此,调用程序进程将命令行分隔为in-to参数(这不是单行,就像在windows中一样)。
现在你需要对它们进行排序: