我有这个功能:
void simple_shell::parse_command(char* cmd, char** cmdTokens) {
// TODO: tokenize the command string into arguments
char *token = strtok(cmd, " ");
int count = 0;
while (token != NULL) {
count = count + 1;
cout << token << endl;
token = strtok(NULL, " ");
}
char *secondToken = strtok(cmd, " ");
*cmdTokens = new char[count];
count = 0;
while (secondToken != NULL) {
cmdTokens[count] = secondToken;
cout << secondToken << endl;
secondToken = strtok(NULL, " ");
count = count + 1;
}
}
我基本上使用cmd并将其作为分隔符与空格分开。然后,在while循环中,我打印每个标记。
第一个while循环和第一个标记可以正常工作。如果字符串是“hello world”,那么我在两个单独的行上得到“hello”和“world”,这正是我想要的。
然而,在第二个循环中,出现了问题,我只得到“你好”,而没有别的。这是怎么回事?
答案 0 :(得分:2)
strtok()
对以null结尾的字符串作为输入进行操作,但修改字符串。当它在字符串中找到分隔符时,它会在分隔符所在的位置注入一个空字符,并返回指向注入的nul前面的标记的指针。在下一次使用NULL输入指针调用时,搜索在先前注入的nul。
因此,当您运行第一个循环时,所有内容都正确输出,但strtok()
已更改cmd
的内容以使用nul字符替换所有空格字符。因此,当你运行第二个循环时,它不能再在cmd
中找到任何空格字符,并且在第一个标记到达后的nul字符处停止查看,即使它不是真正的最后一个在字符串中。
所以,你有几个选择。
您可以在每个循环上复制cmd
。您还必须复制存储在输出数组中的每个标记,因为它们将指向临时内存:
int simple_shell::parse_command(char* cmd, char*** cmdTokens)
{
*cmdTokens = NULL;
char *dup = strdup(cmd);
if (!dup)
return -1;
int count = 0;
char *token = strtok(dup, " ");
while (token)
{
++count;
token = strtok(NULL, " ");
}
free(dup);
try {
*cmdTokens = new char*[count];
}
catch (const std::bad_alloc&) {
return -1;
}
if (count > 0)
{
dup = strdup(cmd);
if (!dup)
{
delete[] *cmdTokens;
return -1;
}
token = strtok(dup, " ");
count = 0;
while (token)
{
cmdTokens[count] = strdup(token);
if (!cmdTokens[count])
{
for(int i = 0; i < count; ++i)
free(cmdTokens[i]);
delete[] *cmdTokens;
return -1;
}
++count;
token = strtok(NULL, " ");
}
}
return count;
}
char** tokens;
int numTokens = shell.parse_command("hello world", &tokens);
if (numTokens != -1)
{
for (int i = 0; i < numTokens; ++i)
{
std::cout << tokens[i] << std::endl;
free(tokens[i]);
}
delete[] tokens;
}
或者,您可以停止使用strtok()
并使用破坏性较小的方法来提取子字符串。但是,您仍然需要复制单个标记,因为不再有cmd
注入任何空字符:
const char* nextToken(const char *str)
{
if (!str)
return NULL;
while (*str == ' ')
++str;
if (*str == '\0')
return NULL;
return str;
}
const char* endOfToken(const char *str)
{
if (!str)
return NULL;
while ((*str != ' ') && (*str != '\0'))
++str;
return ptr;
}
int simple_shell::parse_command(char* cmd, char*** cmdTokens)
{
*cmdTokens = NULL;
char *ptr = cmd;
int count = 0;
const char *token = nextToken(ptr);
while (token)
{
++count;
token = nextToken(endOfToken(token));
}
try {
*cmdTokens = new char*[count];
}
catch (const std::bad_alloc&) {
return -1;
}
if (count > 0)
{
ptr = cmd;
count = 0;
token = nextToken(ptr);
while (token)
{
const char *end = endOfToken(token);
int len = (end-token);
try {
cmdTokens[count] = new char[len+1];
}
catch (const std::bad_alloc&) {
for(int i = 0; i < count; ++i)
delete[] cmdTokens[i];
return -1;
}
memcpy(cmdTokens[count], token, len);
cmdTokens[count][len] = '\0';
++count;
token = nextToken(end);
}
}
return count;
}
char** tokens;
int numTokens = shell.parse_command("hello world", &tokens);
if (numTokens != -1)
{
for (int i = 0; i < numTokens; ++i)
{
std::cout << tokens[i] << std::endl;
delete[] tokens[i];
}
delete[] tokens;
}
或者,如果你不想复制令牌,只需返回指向它们的指针,以及它们的长度:
struct token_info
{
char* token;
int length;
}
const char* nextToken(const char *str)
{
if (!str)
return NULL;
while (*str == ' ')
++str;
if (*str == '\0')
return NULL;
return str;
}
const char* endOfToken(const char *str)
{
if (!str)
return NULL;
while ((*str != ' ') && (*str != '\0'))
++str;
return ptr;
}
int simple_shell::parse_command(char* cmd, token_info** cmdTokens)
{
*cmdTokens = NULL;
char *ptr = cmd;
int count = 0;
const char *token = nextToken(ptr);
while (token)
{
++count;
token = nextToken(endOfToken(token));
}
try {
*cmdTokens = new token_info[count];
}
catch (const std:bad_alloc&) {
return -1;
}
if (count > 0)
{
ptr = cmd;
count = 0;
token = nextToken(ptr);
while (token)
{
const char *end = endOfToken(token);
cmdTokens[count].token = const_cast<char*>(token);
cmdTokens[count].length = (end-token);
++count;
token = nextToken(end);
}
}
return count;
}
token_info* tokens;
int numTokens = shell.parse_command("hello world", &tokens);
if (numTokens != -1)
{
for (int i = 0; i < numTokens; ++i)
std::cout << std::string(tokens[i].token, tokens[i].length) << std::endl;
delete[] tokens;
}
但是,由于你显然正在使用C ++,你应该停止使用这种古老的C风格方法,而是使用更现代的C ++实践,例如:
std::vector<std::string> simple_shell::parse_command(const std::string &cmd)
{
std::istringstream iss(cmd);
return std::vector<std::string>(
std::istream_iterator<std::string>(iss),
std::istream_iterator<std::string>()
);
}
std::vector<std::string> tokens = shell.parse_command("hello world");
for (auto &token: tokens)
std::cout << token << std::endl;