在 for 循环内的 strsep() 之后调用 printf() 会导致段错误
Calling printf() after strsep() inside for loop causes a segfault
我正在用 C 语言编写自己的 UNIX shell,我正在尝试添加对在引号内传递多词参数的支持(即 echo "This is a test"
)。在您可以在下面看到的我当前的函数 (parseCommandWords
) 中,我成功地分离了通过输入参数传递给函数的单词,并通过 strsep()
适当地更新了输入。但是,一旦 printf()
调用 运行s 并打印 word
和 input
的正确值,就会抛出分段错误。它永远不会到达 printf 下面的任何 if 语句,在它下面添加任何东西,根本就不会 运行。我看不出是什么导致了这个问题。例如用 input = ls
测试它(简单命令),它会打印出 word = ls | input = (null)
正如你所期望的那样。
parsedWords
参数最初是 NULL 字符串数组,参数在传递给函数之前也经过验证。
更新 #1: 问题几乎肯定与 strcpy(parsedWords[i],word)
有关。将其更改为 parsedWords[i] = word
不会导致段错误,但当然,一旦我们退出该函数,它就会失去其价值。当它通知我非法 read/write.
时,我能够使用 Valgrind 查明这一点
更新 2: 我认为问题在于我在 parseInput
中初始化 args
char* 数组的方式。使用 NULL 初始化每个 char* 然后尝试使用 strcpy 在该位置写入应该是导致问题的原因,对吗?像这样为每个字符串动态分配内存解决了这个问题:
char *args[MAX_NUM_OF_COMMAND_WORDS];
int i;
for(i=0; i < MAX_NUM_OF_COMMAND_WORDS; i++) {
args[i] = (char *)malloc(50*sizeof(char));
}
完整代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
#include "cs345sh.h"
/**
* Counts how many times the given char is present
* in the given string.
* @param input The string in which to look for
* @param lookupChar The char whose occurences to count
* @return The number of occurences of the given char
**/
int countCharOccurences(char *input, char lookupChar)
{
char *str = input;
int count = 0;
int i;
for (i = 0; str[i]; i++)
{
if (str[i] == lookupChar)
count++;
}
return count;
}
/**
* Parses the available command words in the given command and places
* them in the given array.
* @param input The initial string to split that contains the command.
* @param parsedWords The final parsed commands.
**/
void parseCommandWords(char *input, char **parsedWords)
{
int i;
for (i = 0; i < MAX_NUM_OF_COMMAND_WORDS; i++)
{
char *word = (char *)malloc(100 * sizeof(char)); // max 100 chars
if (!word)
{
perror("Failed to allocate memory!\n");
exit(EXIT_FAILURE);
}
if (input[0] == '\"')
{
char *inptPtr = input;
int charCnt = 0;
do
{
inptPtr++;
charCnt++;
} while (inptPtr[0] != '\"');
charCnt++; // include final "
strncpy(word, input, charCnt);
// check if there are chars left to parse or not
if (++inptPtr != NULL)
{
input = ++inptPtr; // start after the ending "
}
else
{
input = "";
}
printf("word after loop = %s\ninput = %s\n", word, input);
strcpy(parsedWords[i],word);
free(word);
continue;
}
word = strsep(&input, " ");
printf("word = %s | input = %s\n",word,input);
if (word == NULL)
{
free(word);
break; // there was nothing to split
}
if (strlen(word) == 0)
{
free(word);
i--; // read an empty command, re-iterate
continue;
}
printf("before cpy");
strcpy(parsedWords[i],word);
printf("word = %s | parsedwords[i] = %s\n",word,parsedWords[i]);
free(word);
if(input == NULL) break;
}
printf("exiting parser");
}
/**
* Parses the available commands in the given string and places
* them in the given array.
* @param input The initial string to split that contains the commands.
* @param parsedWords The final parsed commands.
**/
void parseMultipleCommands(char *input, char **parsedCommands)
{
int numOfSemicolons = countCharOccurences(input, ';');
int i;
for (i = 0; i < numOfSemicolons + 1; i++)
{
char *word = strsep(&input, ";");
if (word == NULL)
break;
if (strlen(word) == 0)
{
i--;
continue;
}
parsedCommands[i] = word;
}
}
char *removeLeadingWhitespace(char *input)
{
while (*input == ' ')
{
input++;
}
return input;
}
/**
* Splits the given string at each pipe char occurance and places
* each command in the given array.
* @param input The initial string to split
* @param inptParsed The final parsed commands split at the pipe chars
* @return Returns 0 if no pipe chars were found or 1 if the operatio was successful.
**/
int splitAtPipe(char *input, char **inptParsed)
{
int numOfPipes = countCharOccurences(input, '|');
int i;
// create a copy of the given input in order to preserver the original
char *inpt = (char *)malloc(MAX_INPUT_SIZE * sizeof(char));
strcpy(inpt, input);
for (i = 0; i < numOfPipes + 1; i++)
{
char *word = strsep(&inpt, "|");
if (word == NULL)
break;
if (strlen(word) == 0)
{
i--;
continue;
}
word = removeLeadingWhitespace(word);
inptParsed[i] = word;
}
return 1;
}
/**
* Handles the execution of custom commands (i.e. cd, exit).
* @param cmdInfo An array containing the command to execute in the first position, and the arguments
* to execute with in the rest of the array.
* @return Returns 0 if the command couldn't be executed, or 1 otherwise.
**/
int handleCustomCommands(char **cmdInfo)
{
int numOfCustomCommands = 2;
char *customCommands[numOfCustomCommands];
customCommands[0] = "cd";
customCommands[1] = "exit";
int i;
for (i = 0; i < numOfCustomCommands; i++)
{
// find the command to execute
if (strcmp(cmdInfo[0], customCommands[i]) == 0)
break;
}
switch (i)
{
case 0:
if (chdir(cmdInfo[1]) == -1)
return 0;
else
return 1;
case 1:
exit(0);
return 1;
default:
break;
}
return 0;
}
/**
* Displays the shell prompt in the following format:
* <user>@cs345sh/<dir>$
**/
void displayPrompt()
{
char *user = getlogin();
char cwd[512]; // support up to 512 chars long dir paths
if (getcwd(cwd, sizeof(cwd)) == NULL)
{
perror("error retrieving current working directory.");
exit(-1);
}
else if (user == NULL)
{
perror("error getting currently logged in user.");
exit(-1);
}
else
{
printf("%s@cs345%s$ ", user, cwd);
}
}
void execSystemCommand(char **args)
{
// create an identical child process
pid_t pid = fork();
if (pid == -1)
{
perror("\nFailed to fork child..");
exit(EXIT_FAILURE);
}
else if (pid == 0)
{
if (execvp(args[0], args) < 0)
{
perror("Could not execute given command..");
}
exit(EXIT_FAILURE);
}
else
{
// wait for the child process to finish
wait(NULL);
return;
}
}
void execPipedCommands(char *input, char **commands)
{
int numOfPipes = countCharOccurences(input, '|');
int fds[2 * numOfPipes]; // two file descriptors per pipe needed for interprocess communication
int i;
pid_t cpid;
// initialize all pipes and store their respective fds in the appropriate place in the array
for (i = 0; i < numOfPipes; i++)
{
if (pipe(fds + 2 * i) == -1)
{
perror("Failed to create file descriptors for pipe commands!\n");
exit(EXIT_FAILURE);
}
}
for (i = 0; i < numOfPipes + 1; i++)
{
if (commands[i] == NULL)
break;
char *args[MAX_NUM_OF_COMMAND_WORDS] = {
NULL,
};
parseCommandWords(commands[i], args);
cpid = fork(); // start a child process
if (cpid == -1)
{
perror("Failed to fork..\n");
exit(EXIT_FAILURE);
}
if (cpid == 0)
{ // child process is executing
if (i != 0)
{ // if this is not the first command in the chain
// duplicate the file descriptor to read from the previous command's output
if (dup2(fds[(i - 1) * 2], STDIN_FILENO) < 0)
{
perror("Failed to read input from previous command..\n");
exit(EXIT_FAILURE);
}
}
// if this is not the last command in the chain
if (i != numOfPipes && commands[i + 1] != NULL)
{
// duplicate write file descriptor in order to output to the next command
if (dup2(fds[(i * 2 + 1)], STDOUT_FILENO) < 0)
{
perror("Failed to write output for the next command..\n");
exit(EXIT_FAILURE);
}
}
// close the pipes
int j;
for (j = 0; j < numOfPipes + 1; j++)
{ // close all copies of the file descriptors
close(fds[j]);
}
// execute command
if (execvp(args[0], args) < 0)
{
perror("Failed to execute given piped command");
return;
}
}
}
// parent closes all original file descriptors
for (i = 0; i < numOfPipes + 1; i++)
{
close(fds[i]);
}
// parent waits for all child processes to finish
for (i = 0; i < numOfPipes + 1; i++)
wait(0);
}
void parseInput(char *input)
{
if (strchr(input, '|') != NULL)
{ // possibly piped command(s)
char *commands[MAX_NUM_OF_COMMANDS] = {
NULL,
};
splitAtPipe(input, commands);
execPipedCommands(input, commands);
}
else if (strchr(input, ';') != NULL)
{ // possibly multiple command(s)
char *commands[MAX_NUM_OF_COMMANDS] = {
NULL,
};
parseMultipleCommands(input, commands);
int i;
for (i = 0; i < MAX_NUM_OF_COMMANDS; i++)
{
if (commands[i] == NULL)
break;
// single command
char *args[MAX_NUM_OF_COMMAND_WORDS] = {
NULL,
};
parseCommandWords(commands[i], args);
if (handleCustomCommands(args) == 0)
{
execSystemCommand(args);
}
}
}
else
{
// single command
char *args[MAX_NUM_OF_COMMAND_WORDS] = {
NULL,
};
parseCommandWords(input, args);
printf("parsed! arg[0] = %s\n",args[0]);
if (handleCustomCommands(args) == 0)
{
execSystemCommand(args);
}
}
}
int main()
{
char *inputBuf = NULL; // getline will allocate the buffer
size_t inputLen = 0;
while (1)
{
displayPrompt();
if (getline(&inputBuf, &inputLen, stdin) == -1)
{
perror("Error reading input.");
exit(EXIT_FAILURE);
}
if (*inputBuf == '\n')
continue;
else
{
// remove the \n at the end of the read line ()
inputBuf[strcspn(inputBuf, "\n")] = '[=11=]';
parseInput(inputBuf);
}
}
return 0;
}
这是最小的可重现示例:
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
#include "cs345sh.h"
/**
* Counts how many times the given char is present
* in the given string.
* @param input The string in which to look for
* @param lookupChar The char whose occurences to count
* @return The number of occurences of the given char
**/
int countCharOccurences(char *input, char lookupChar)
{
char *str = input;
int count = 0;
int i;
for (i = 0; str[i]; i++)
{
if (str[i] == lookupChar)
count++;
}
return count;
}
/**
* Parses the available command words in the given command and places
* them in the given array.
* @param input The initial string to split that contains the command.
* @param parsedWords The final parsed commands.
**/
void parseCommandWords(char *input, char **parsedWords)
{
int i;
for (i = 0; i < MAX_NUM_OF_COMMAND_WORDS; i++)
{
char *word = (char *)malloc(100 * sizeof(char)); // max 100 chars
if (!word)
{
perror("Failed to allocate memory!\n");
exit(EXIT_FAILURE);
}
if (input[0] == '\"')
{
char *inptPtr = input;
int charCnt = 0;
do
{
inptPtr++;
charCnt++;
} while (inptPtr[0] != '\"');
charCnt++; // include final "
strncpy(word, input, charCnt);
// check if there are chars left to parse or not
if (++inptPtr != NULL)
{
input = ++inptPtr; // start after the ending "
}
else
{
input = "";
}
printf("word after loop = %s\ninput = %s\n", word, input);
strcpy(parsedWords[i],word);
free(word);
continue;
}
word = strsep(&input, " ");
printf("word = %s | input = %s\n",word,input);
if (word == NULL)
{
free(word);
break; // there was nothing to split
}
if (strlen(word) == 0)
{
free(word);
i--; // read an empty command, re-iterate
continue;
}
printf("before cpy");
strcpy(parsedWords[i],word);
printf("word = %s | parsedwords[i] = %s\n",word,parsedWords[i]);
free(word);
if(input == NULL) break;
}
printf("exiting parser");
}
/**
* Handles the execution of custom commands (i.e. cd, exit).
* @param cmdInfo An array containing the command to execute in the first position, and the arguments
* to execute with in the rest of the array.
* @return Returns 0 if the command couldn't be executed, or 1 otherwise.
**/
int handleCustomCommands(char **cmdInfo)
{
int numOfCustomCommands = 2;
char *customCommands[numOfCustomCommands];
customCommands[0] = "cd";
customCommands[1] = "exit";
int i;
for (i = 0; i < numOfCustomCommands; i++)
{
// find the command to execute
if (strcmp(cmdInfo[0], customCommands[i]) == 0)
break;
}
switch (i)
{
case 0:
if (chdir(cmdInfo[1]) == -1)
return 0;
else
return 1;
case 1:
exit(0);
return 1;
default:
break;
}
return 0;
}
/**
* Displays the shell prompt in the following format:
* <user>@cs345sh/<dir>$
**/
void displayPrompt()
{
char *user = getlogin();
char cwd[512]; // support up to 512 chars long dir paths
if (getcwd(cwd, sizeof(cwd)) == NULL)
{
perror("error retrieving current working directory.");
exit(-1);
}
else if (user == NULL)
{
perror("error getting currently logged in user.");
exit(-1);
}
else
{
printf("%s@cs345%s$ ", user, cwd);
}
}
void execSystemCommand(char **args)
{
// create an identical child process
pid_t pid = fork();
if (pid == -1)
{
perror("\nFailed to fork child..");
exit(EXIT_FAILURE);
}
else if (pid == 0)
{
if (execvp(args[0], args) < 0)
{
perror("Could not execute given command..");
}
exit(EXIT_FAILURE);
}
else
{
// wait for the child process to finish
wait(NULL);
return;
}
}
void parseInput(char *input)
{
// single command
char *args[MAX_NUM_OF_COMMAND_WORDS] = {
NULL,
};
parseCommandWords(input, args);
printf("parsed! arg[0] = %s\n",args[0]);
if (handleCustomCommands(args) == 0)
{
execSystemCommand(args);
}
}
int main()
{
char *inputBuf = NULL; // getline will allocate the buffer
size_t inputLen = 0;
while (1)
{
displayPrompt();
if (getline(&inputBuf, &inputLen, stdin) == -1)
{
perror("Error reading input.");
exit(EXIT_FAILURE);
}
if (*inputBuf == '\n')
continue;
else
{
// remove the \n at the end of the read line ()
inputBuf[strcspn(inputBuf, "\n")] = '[=12=]';
parseInput(inputBuf);
}
}
return 0;
}
头文件:
#define MAX_NUM_OF_COMMAND_WORDS 50 // usual num of maximum command arguments is 9 (but is system dependent)
#define MAX_NUM_OF_COMMANDS 20 // what could it be hmm
#define MAX_INPUT_SIZE 1000 // num of max chars to read
/**
* Counts how many times the given char is present
* in the given string.
* @param input The string in which to look for
* @param lookupChar The char whose occurences to count
* @return The number of occurences of the given char
**/
int countCharOccurences(char* input, char lookupChar);
/**
* Parses the available command words in the given command and places
* them in the given array.
* @param input The initial string to split that contains the command.
* @param parsedWords The final parsed commands.
**/
void parseCommandWords(char *input, char** parsedWords);
/**
* Parses the available commands in the given string and places
* them in the given array.
* @param input The initial string to split that contains the commands.
* @param parsedWords The final parsed commands.
**/
void parseMultipleCommands(char *input, char **parsedCommands);
/**
* Splits the given string at each pipe char and places
* each command in the given array.
* @param input The initial string to split
* @param inptParsed The final parsed commands split at the pipe chars
* @return Returns 0 if no pipe chars were found or 1 if the operation was successful.
**/
int splitAtPipe(char *input, char** inptParsed);
/**
* Handles the execution of custom commands (i.e. cd, exit).
* @param cmdInfo An array containing the command to execute in the first position, and the arguments
* to execute with in the rest of the array.
* @return Returns 0 if the command couldn't be executed, or 1 otherwise.
**/
int handleCustomCommands(char **command);
/**
* Displays the shell prompt in the following format:
* <user>@cs345sh/<dir>$
**/
void displayPrompt();
void execPipedCommands(char*, char**);
/**
* Removes any trailing whitespace from the given string
* and returns a pointer at the beginning of the new string.
* @param input The string to remove whitespace from
*/
char* removeLeadingWhitespace(char *input) ;
与strcpy
相反,函数strncpy
不一定会向目标数组添加终止空字符。
由于 charCnt
似乎小于或等于 strlen(input)
,因此行
strncpy(word, input, charCnt);
不会将终止空字符写入word
。因此,行
printf("word after loop = %s\ninput = %s\n", word, input);
将调用未定义的行为,因为 %s
格式说明符需要一个以 null 结尾的字符串。
缺少初始化
inputLen
在 getline()
时间不确定。初始化它。
*lineptr 可以包含一个
指向大小为 *n 字节的 malloc(3) 分配缓冲区的指针。
char *inputBuf;
// size_t inputLen;
size_t inputLen = MAX_INPUT_SIZE;
inputBuf = (char*) malloc(MAX_INPUT_SIZE * sizeof(char));
...
if (getline(&inputBuf, &inputLen, stdin) == -1) {
// Even better
char *inputBuf = NULL;
size_t inputLen = 0;
// Not needed
// inputBuf = (char*) malloc(MAX_INPUT_SIZE * sizeof(char));
...
if (getline(&inputBuf, &inputLen, stdin) == -1) {
可能是其他问题
使用 Valgrind 我能够查明问题是由于我没有为我的 args
数组正确分配内存,然后尝试将内存与 strcpy
一起使用。更具体地说:
使用时char *args[MAX_NUM_OF_COMMAND_WORDS] = { NULL,}
我不是为参数本身分配内存,而是为指针分配内存。
这会导致段错误,因为 strcpy(parsedWords[i],word);
会尝试写入无效内存(因为 parsedWords[i]
将为 NULL)。我重构了代码,以便我只为我需要的 args 分配内存,而不是在我什至不知道我是否需要那么多的时候盲目地为 50 个 args 分配内存。然后我 return 从 parseCommandWords()
函数的给定命令中找到的 args 计数,然后用于释放分配的内存。
修改后的代码(21 年 9 月 11 日更新为最终版本):
/**
* Parses the available command words in the given command and places
* them in the given array.
* @param input The initial string to split that contains the command.
* @param parsedWords An array to every command word.
* @return The number of words in the given command
**/
int parseCommandWords(char *input, char **parsedWords)
{
int i;
int cnt = 0;
for (i = 0; i < MAX_NUM_OF_COMMAND_WORDS; i++)
{
char word[MAX_NUM_OF_COMMAND_WORDS];
input = removeLeadingWhitespace(input);
if (strlen(input) == 0)
break;
if (input[0] == '\"')
{
char *inptPtr = input + 1; // start after the beginning " char
int charCnt = 0;
while (inptPtr[0] != '\"')
{
inptPtr++;
charCnt++;
}
if (charCnt >= MAX_NUM_OF_COMMAND_WORDS)
{
perror("Quoted argument was too long!\n");
exit(EXIT_FAILURE);
}
strncpy(word, input + 1, charCnt); // input+1 : start after the beginning " and charCnt: end before the closing "
word[charCnt] = '[=10=]'; // add null terminator
// check if there are chars left to parse or not
if (strlen(++inptPtr) > 0)
{
input = inptPtr;
}
else
{
input = NULL;
}
parsedWords[i] = (char *)malloc(MAX_NUM_OF_COMMAND_WORDS * sizeof(char));
cnt++;
strcpy(parsedWords[i], word);
if (input == NULL || strlen(input) == 0)
return cnt;
else
continue;
}
strcpy(word, strsep(&input, " "));
if (word == NULL)
break; // nothing to split
if (strlen(word) == 0) // read an empty command, re-iterate
{
i--;
continue;
}
parsedWords[i] = (char *)malloc(MAX_NUM_OF_COMMAND_WORDS * sizeof(char));
if (!parsedWords[i])
{
perror("Failed to allocate memory for command\n");
exit(EXIT_FAILURE);
}
cnt++;
strcpy(parsedWords[i], word);
if (input == NULL || strlen(input) == 0)
break;
}
return cnt;
}
/**
* Executes the given commands after parsing them according to
* their type (i.e. pipes, redirection, etc.).
* @param input A line read from the shell containing commands to execute
* */
void parseInput(char *input)
{
if (strchr(input, '|') != NULL)
{
// possibly piped command(s)
char *commands[MAX_NUM_OF_COMMANDS] = {
NULL,
};
int numOfCmds = splitAtPipe(input, commands);
execPipedCommands(input, commands);
int i;
for (i = 0; i < numOfCmds; i++)
if (commands[i] != NULL)
free(commands[i]);
}
else if (strchr(input, '>') != NULL || strchr(input, '<') != NULL)
{ // no need to check for >> since we check for >
// redirection commands
char *commands[MAX_NUM_OF_REDIR_CMDS] = {
NULL,
};
char *delim = (char *)malloc(3 * sizeof(char));
if (strstr(input, ">>"))
strcpy(delim, ">>");
else if (strchr(input, '>'))
strcpy(delim, ">");
else
strcpy(delim, "<");
splitAtRedirectionDelim(input, commands, delim);
execRedirectionCommands(input, commands, delim);
int i;
for (i = 0; i < MAX_NUM_OF_REDIR_CMDS; i++)
if (commands[i] != NULL)
free(commands[i]);
free(delim);
}
else if (strchr(input, ';') != NULL)
{
// possibly multiple command(s)
char *commands[MAX_NUM_OF_COMMANDS] = {
NULL,
};
int numOfCmds = parseMultipleCommands(input, commands);
int i;
for (i = 0; i < numOfCmds; i++)
{
if (commands[i] == NULL)
break;
// single command
char *args[MAX_NUM_OF_COMMAND_WORDS] = {
NULL,
};
int numOfArgs = parseCommandWords(commands[i], args);
if (handleCustomCommands(args,numOfArgs,input) == 0)
{
execSystemCommand(args);
}
int j;
for (j = 0; j < numOfArgs; j++)
{
free(args[j]);
}
if (commands[i] != NULL)
free(commands[i]);
}
}
else
{
// single command
char *args[MAX_NUM_OF_COMMAND_WORDS] = {
NULL,
};
int numOfArgs = parseCommandWords(input, args);
if (handleCustomCommands(args,numOfArgs,input) == 0)
{
execSystemCommand(args);
}
int i;
for (i = 0; i < numOfArgs; i++)
{
free(args[i]);
}
}
}
正如很多人指出的那样,我的代码还包含很多其他问题(mem 问题、处理引用 args 的逻辑有问题等),所以我将听取他们的意见,采取退后一步,在继续之前尝试分段测试所有内容。
我正在用 C 语言编写自己的 UNIX shell,我正在尝试添加对在引号内传递多词参数的支持(即 echo "This is a test"
)。在您可以在下面看到的我当前的函数 (parseCommandWords
) 中,我成功地分离了通过输入参数传递给函数的单词,并通过 strsep()
适当地更新了输入。但是,一旦 printf()
调用 运行s 并打印 word
和 input
的正确值,就会抛出分段错误。它永远不会到达 printf 下面的任何 if 语句,在它下面添加任何东西,根本就不会 运行。我看不出是什么导致了这个问题。例如用 input = ls
测试它(简单命令),它会打印出 word = ls | input = (null)
正如你所期望的那样。
parsedWords
参数最初是 NULL 字符串数组,参数在传递给函数之前也经过验证。
更新 #1: 问题几乎肯定与 strcpy(parsedWords[i],word)
有关。将其更改为 parsedWords[i] = word
不会导致段错误,但当然,一旦我们退出该函数,它就会失去其价值。当它通知我非法 read/write.
更新 2: 我认为问题在于我在 parseInput
中初始化 args
char* 数组的方式。使用 NULL 初始化每个 char* 然后尝试使用 strcpy 在该位置写入应该是导致问题的原因,对吗?像这样为每个字符串动态分配内存解决了这个问题:
char *args[MAX_NUM_OF_COMMAND_WORDS];
int i;
for(i=0; i < MAX_NUM_OF_COMMAND_WORDS; i++) {
args[i] = (char *)malloc(50*sizeof(char));
}
完整代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
#include "cs345sh.h"
/**
* Counts how many times the given char is present
* in the given string.
* @param input The string in which to look for
* @param lookupChar The char whose occurences to count
* @return The number of occurences of the given char
**/
int countCharOccurences(char *input, char lookupChar)
{
char *str = input;
int count = 0;
int i;
for (i = 0; str[i]; i++)
{
if (str[i] == lookupChar)
count++;
}
return count;
}
/**
* Parses the available command words in the given command and places
* them in the given array.
* @param input The initial string to split that contains the command.
* @param parsedWords The final parsed commands.
**/
void parseCommandWords(char *input, char **parsedWords)
{
int i;
for (i = 0; i < MAX_NUM_OF_COMMAND_WORDS; i++)
{
char *word = (char *)malloc(100 * sizeof(char)); // max 100 chars
if (!word)
{
perror("Failed to allocate memory!\n");
exit(EXIT_FAILURE);
}
if (input[0] == '\"')
{
char *inptPtr = input;
int charCnt = 0;
do
{
inptPtr++;
charCnt++;
} while (inptPtr[0] != '\"');
charCnt++; // include final "
strncpy(word, input, charCnt);
// check if there are chars left to parse or not
if (++inptPtr != NULL)
{
input = ++inptPtr; // start after the ending "
}
else
{
input = "";
}
printf("word after loop = %s\ninput = %s\n", word, input);
strcpy(parsedWords[i],word);
free(word);
continue;
}
word = strsep(&input, " ");
printf("word = %s | input = %s\n",word,input);
if (word == NULL)
{
free(word);
break; // there was nothing to split
}
if (strlen(word) == 0)
{
free(word);
i--; // read an empty command, re-iterate
continue;
}
printf("before cpy");
strcpy(parsedWords[i],word);
printf("word = %s | parsedwords[i] = %s\n",word,parsedWords[i]);
free(word);
if(input == NULL) break;
}
printf("exiting parser");
}
/**
* Parses the available commands in the given string and places
* them in the given array.
* @param input The initial string to split that contains the commands.
* @param parsedWords The final parsed commands.
**/
void parseMultipleCommands(char *input, char **parsedCommands)
{
int numOfSemicolons = countCharOccurences(input, ';');
int i;
for (i = 0; i < numOfSemicolons + 1; i++)
{
char *word = strsep(&input, ";");
if (word == NULL)
break;
if (strlen(word) == 0)
{
i--;
continue;
}
parsedCommands[i] = word;
}
}
char *removeLeadingWhitespace(char *input)
{
while (*input == ' ')
{
input++;
}
return input;
}
/**
* Splits the given string at each pipe char occurance and places
* each command in the given array.
* @param input The initial string to split
* @param inptParsed The final parsed commands split at the pipe chars
* @return Returns 0 if no pipe chars were found or 1 if the operatio was successful.
**/
int splitAtPipe(char *input, char **inptParsed)
{
int numOfPipes = countCharOccurences(input, '|');
int i;
// create a copy of the given input in order to preserver the original
char *inpt = (char *)malloc(MAX_INPUT_SIZE * sizeof(char));
strcpy(inpt, input);
for (i = 0; i < numOfPipes + 1; i++)
{
char *word = strsep(&inpt, "|");
if (word == NULL)
break;
if (strlen(word) == 0)
{
i--;
continue;
}
word = removeLeadingWhitespace(word);
inptParsed[i] = word;
}
return 1;
}
/**
* Handles the execution of custom commands (i.e. cd, exit).
* @param cmdInfo An array containing the command to execute in the first position, and the arguments
* to execute with in the rest of the array.
* @return Returns 0 if the command couldn't be executed, or 1 otherwise.
**/
int handleCustomCommands(char **cmdInfo)
{
int numOfCustomCommands = 2;
char *customCommands[numOfCustomCommands];
customCommands[0] = "cd";
customCommands[1] = "exit";
int i;
for (i = 0; i < numOfCustomCommands; i++)
{
// find the command to execute
if (strcmp(cmdInfo[0], customCommands[i]) == 0)
break;
}
switch (i)
{
case 0:
if (chdir(cmdInfo[1]) == -1)
return 0;
else
return 1;
case 1:
exit(0);
return 1;
default:
break;
}
return 0;
}
/**
* Displays the shell prompt in the following format:
* <user>@cs345sh/<dir>$
**/
void displayPrompt()
{
char *user = getlogin();
char cwd[512]; // support up to 512 chars long dir paths
if (getcwd(cwd, sizeof(cwd)) == NULL)
{
perror("error retrieving current working directory.");
exit(-1);
}
else if (user == NULL)
{
perror("error getting currently logged in user.");
exit(-1);
}
else
{
printf("%s@cs345%s$ ", user, cwd);
}
}
void execSystemCommand(char **args)
{
// create an identical child process
pid_t pid = fork();
if (pid == -1)
{
perror("\nFailed to fork child..");
exit(EXIT_FAILURE);
}
else if (pid == 0)
{
if (execvp(args[0], args) < 0)
{
perror("Could not execute given command..");
}
exit(EXIT_FAILURE);
}
else
{
// wait for the child process to finish
wait(NULL);
return;
}
}
void execPipedCommands(char *input, char **commands)
{
int numOfPipes = countCharOccurences(input, '|');
int fds[2 * numOfPipes]; // two file descriptors per pipe needed for interprocess communication
int i;
pid_t cpid;
// initialize all pipes and store their respective fds in the appropriate place in the array
for (i = 0; i < numOfPipes; i++)
{
if (pipe(fds + 2 * i) == -1)
{
perror("Failed to create file descriptors for pipe commands!\n");
exit(EXIT_FAILURE);
}
}
for (i = 0; i < numOfPipes + 1; i++)
{
if (commands[i] == NULL)
break;
char *args[MAX_NUM_OF_COMMAND_WORDS] = {
NULL,
};
parseCommandWords(commands[i], args);
cpid = fork(); // start a child process
if (cpid == -1)
{
perror("Failed to fork..\n");
exit(EXIT_FAILURE);
}
if (cpid == 0)
{ // child process is executing
if (i != 0)
{ // if this is not the first command in the chain
// duplicate the file descriptor to read from the previous command's output
if (dup2(fds[(i - 1) * 2], STDIN_FILENO) < 0)
{
perror("Failed to read input from previous command..\n");
exit(EXIT_FAILURE);
}
}
// if this is not the last command in the chain
if (i != numOfPipes && commands[i + 1] != NULL)
{
// duplicate write file descriptor in order to output to the next command
if (dup2(fds[(i * 2 + 1)], STDOUT_FILENO) < 0)
{
perror("Failed to write output for the next command..\n");
exit(EXIT_FAILURE);
}
}
// close the pipes
int j;
for (j = 0; j < numOfPipes + 1; j++)
{ // close all copies of the file descriptors
close(fds[j]);
}
// execute command
if (execvp(args[0], args) < 0)
{
perror("Failed to execute given piped command");
return;
}
}
}
// parent closes all original file descriptors
for (i = 0; i < numOfPipes + 1; i++)
{
close(fds[i]);
}
// parent waits for all child processes to finish
for (i = 0; i < numOfPipes + 1; i++)
wait(0);
}
void parseInput(char *input)
{
if (strchr(input, '|') != NULL)
{ // possibly piped command(s)
char *commands[MAX_NUM_OF_COMMANDS] = {
NULL,
};
splitAtPipe(input, commands);
execPipedCommands(input, commands);
}
else if (strchr(input, ';') != NULL)
{ // possibly multiple command(s)
char *commands[MAX_NUM_OF_COMMANDS] = {
NULL,
};
parseMultipleCommands(input, commands);
int i;
for (i = 0; i < MAX_NUM_OF_COMMANDS; i++)
{
if (commands[i] == NULL)
break;
// single command
char *args[MAX_NUM_OF_COMMAND_WORDS] = {
NULL,
};
parseCommandWords(commands[i], args);
if (handleCustomCommands(args) == 0)
{
execSystemCommand(args);
}
}
}
else
{
// single command
char *args[MAX_NUM_OF_COMMAND_WORDS] = {
NULL,
};
parseCommandWords(input, args);
printf("parsed! arg[0] = %s\n",args[0]);
if (handleCustomCommands(args) == 0)
{
execSystemCommand(args);
}
}
}
int main()
{
char *inputBuf = NULL; // getline will allocate the buffer
size_t inputLen = 0;
while (1)
{
displayPrompt();
if (getline(&inputBuf, &inputLen, stdin) == -1)
{
perror("Error reading input.");
exit(EXIT_FAILURE);
}
if (*inputBuf == '\n')
continue;
else
{
// remove the \n at the end of the read line ()
inputBuf[strcspn(inputBuf, "\n")] = '[=11=]';
parseInput(inputBuf);
}
}
return 0;
}
这是最小的可重现示例:
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
#include "cs345sh.h"
/**
* Counts how many times the given char is present
* in the given string.
* @param input The string in which to look for
* @param lookupChar The char whose occurences to count
* @return The number of occurences of the given char
**/
int countCharOccurences(char *input, char lookupChar)
{
char *str = input;
int count = 0;
int i;
for (i = 0; str[i]; i++)
{
if (str[i] == lookupChar)
count++;
}
return count;
}
/**
* Parses the available command words in the given command and places
* them in the given array.
* @param input The initial string to split that contains the command.
* @param parsedWords The final parsed commands.
**/
void parseCommandWords(char *input, char **parsedWords)
{
int i;
for (i = 0; i < MAX_NUM_OF_COMMAND_WORDS; i++)
{
char *word = (char *)malloc(100 * sizeof(char)); // max 100 chars
if (!word)
{
perror("Failed to allocate memory!\n");
exit(EXIT_FAILURE);
}
if (input[0] == '\"')
{
char *inptPtr = input;
int charCnt = 0;
do
{
inptPtr++;
charCnt++;
} while (inptPtr[0] != '\"');
charCnt++; // include final "
strncpy(word, input, charCnt);
// check if there are chars left to parse or not
if (++inptPtr != NULL)
{
input = ++inptPtr; // start after the ending "
}
else
{
input = "";
}
printf("word after loop = %s\ninput = %s\n", word, input);
strcpy(parsedWords[i],word);
free(word);
continue;
}
word = strsep(&input, " ");
printf("word = %s | input = %s\n",word,input);
if (word == NULL)
{
free(word);
break; // there was nothing to split
}
if (strlen(word) == 0)
{
free(word);
i--; // read an empty command, re-iterate
continue;
}
printf("before cpy");
strcpy(parsedWords[i],word);
printf("word = %s | parsedwords[i] = %s\n",word,parsedWords[i]);
free(word);
if(input == NULL) break;
}
printf("exiting parser");
}
/**
* Handles the execution of custom commands (i.e. cd, exit).
* @param cmdInfo An array containing the command to execute in the first position, and the arguments
* to execute with in the rest of the array.
* @return Returns 0 if the command couldn't be executed, or 1 otherwise.
**/
int handleCustomCommands(char **cmdInfo)
{
int numOfCustomCommands = 2;
char *customCommands[numOfCustomCommands];
customCommands[0] = "cd";
customCommands[1] = "exit";
int i;
for (i = 0; i < numOfCustomCommands; i++)
{
// find the command to execute
if (strcmp(cmdInfo[0], customCommands[i]) == 0)
break;
}
switch (i)
{
case 0:
if (chdir(cmdInfo[1]) == -1)
return 0;
else
return 1;
case 1:
exit(0);
return 1;
default:
break;
}
return 0;
}
/**
* Displays the shell prompt in the following format:
* <user>@cs345sh/<dir>$
**/
void displayPrompt()
{
char *user = getlogin();
char cwd[512]; // support up to 512 chars long dir paths
if (getcwd(cwd, sizeof(cwd)) == NULL)
{
perror("error retrieving current working directory.");
exit(-1);
}
else if (user == NULL)
{
perror("error getting currently logged in user.");
exit(-1);
}
else
{
printf("%s@cs345%s$ ", user, cwd);
}
}
void execSystemCommand(char **args)
{
// create an identical child process
pid_t pid = fork();
if (pid == -1)
{
perror("\nFailed to fork child..");
exit(EXIT_FAILURE);
}
else if (pid == 0)
{
if (execvp(args[0], args) < 0)
{
perror("Could not execute given command..");
}
exit(EXIT_FAILURE);
}
else
{
// wait for the child process to finish
wait(NULL);
return;
}
}
void parseInput(char *input)
{
// single command
char *args[MAX_NUM_OF_COMMAND_WORDS] = {
NULL,
};
parseCommandWords(input, args);
printf("parsed! arg[0] = %s\n",args[0]);
if (handleCustomCommands(args) == 0)
{
execSystemCommand(args);
}
}
int main()
{
char *inputBuf = NULL; // getline will allocate the buffer
size_t inputLen = 0;
while (1)
{
displayPrompt();
if (getline(&inputBuf, &inputLen, stdin) == -1)
{
perror("Error reading input.");
exit(EXIT_FAILURE);
}
if (*inputBuf == '\n')
continue;
else
{
// remove the \n at the end of the read line ()
inputBuf[strcspn(inputBuf, "\n")] = '[=12=]';
parseInput(inputBuf);
}
}
return 0;
}
头文件:
#define MAX_NUM_OF_COMMAND_WORDS 50 // usual num of maximum command arguments is 9 (but is system dependent)
#define MAX_NUM_OF_COMMANDS 20 // what could it be hmm
#define MAX_INPUT_SIZE 1000 // num of max chars to read
/**
* Counts how many times the given char is present
* in the given string.
* @param input The string in which to look for
* @param lookupChar The char whose occurences to count
* @return The number of occurences of the given char
**/
int countCharOccurences(char* input, char lookupChar);
/**
* Parses the available command words in the given command and places
* them in the given array.
* @param input The initial string to split that contains the command.
* @param parsedWords The final parsed commands.
**/
void parseCommandWords(char *input, char** parsedWords);
/**
* Parses the available commands in the given string and places
* them in the given array.
* @param input The initial string to split that contains the commands.
* @param parsedWords The final parsed commands.
**/
void parseMultipleCommands(char *input, char **parsedCommands);
/**
* Splits the given string at each pipe char and places
* each command in the given array.
* @param input The initial string to split
* @param inptParsed The final parsed commands split at the pipe chars
* @return Returns 0 if no pipe chars were found or 1 if the operation was successful.
**/
int splitAtPipe(char *input, char** inptParsed);
/**
* Handles the execution of custom commands (i.e. cd, exit).
* @param cmdInfo An array containing the command to execute in the first position, and the arguments
* to execute with in the rest of the array.
* @return Returns 0 if the command couldn't be executed, or 1 otherwise.
**/
int handleCustomCommands(char **command);
/**
* Displays the shell prompt in the following format:
* <user>@cs345sh/<dir>$
**/
void displayPrompt();
void execPipedCommands(char*, char**);
/**
* Removes any trailing whitespace from the given string
* and returns a pointer at the beginning of the new string.
* @param input The string to remove whitespace from
*/
char* removeLeadingWhitespace(char *input) ;
与strcpy
相反,函数strncpy
不一定会向目标数组添加终止空字符。
由于 charCnt
似乎小于或等于 strlen(input)
,因此行
strncpy(word, input, charCnt);
不会将终止空字符写入word
。因此,行
printf("word after loop = %s\ninput = %s\n", word, input);
将调用未定义的行为,因为 %s
格式说明符需要一个以 null 结尾的字符串。
缺少初始化
inputLen
在 getline()
时间不确定。初始化它。
*lineptr 可以包含一个
指向大小为 *n 字节的 malloc(3) 分配缓冲区的指针。
char *inputBuf;
// size_t inputLen;
size_t inputLen = MAX_INPUT_SIZE;
inputBuf = (char*) malloc(MAX_INPUT_SIZE * sizeof(char));
...
if (getline(&inputBuf, &inputLen, stdin) == -1) {
// Even better
char *inputBuf = NULL;
size_t inputLen = 0;
// Not needed
// inputBuf = (char*) malloc(MAX_INPUT_SIZE * sizeof(char));
...
if (getline(&inputBuf, &inputLen, stdin) == -1) {
可能是其他问题
使用 Valgrind 我能够查明问题是由于我没有为我的 args
数组正确分配内存,然后尝试将内存与 strcpy
一起使用。更具体地说:
使用时char *args[MAX_NUM_OF_COMMAND_WORDS] = { NULL,}
我不是为参数本身分配内存,而是为指针分配内存。
这会导致段错误,因为 strcpy(parsedWords[i],word);
会尝试写入无效内存(因为 parsedWords[i]
将为 NULL)。我重构了代码,以便我只为我需要的 args 分配内存,而不是在我什至不知道我是否需要那么多的时候盲目地为 50 个 args 分配内存。然后我 return 从 parseCommandWords()
函数的给定命令中找到的 args 计数,然后用于释放分配的内存。
修改后的代码(21 年 9 月 11 日更新为最终版本):
/**
* Parses the available command words in the given command and places
* them in the given array.
* @param input The initial string to split that contains the command.
* @param parsedWords An array to every command word.
* @return The number of words in the given command
**/
int parseCommandWords(char *input, char **parsedWords)
{
int i;
int cnt = 0;
for (i = 0; i < MAX_NUM_OF_COMMAND_WORDS; i++)
{
char word[MAX_NUM_OF_COMMAND_WORDS];
input = removeLeadingWhitespace(input);
if (strlen(input) == 0)
break;
if (input[0] == '\"')
{
char *inptPtr = input + 1; // start after the beginning " char
int charCnt = 0;
while (inptPtr[0] != '\"')
{
inptPtr++;
charCnt++;
}
if (charCnt >= MAX_NUM_OF_COMMAND_WORDS)
{
perror("Quoted argument was too long!\n");
exit(EXIT_FAILURE);
}
strncpy(word, input + 1, charCnt); // input+1 : start after the beginning " and charCnt: end before the closing "
word[charCnt] = '[=10=]'; // add null terminator
// check if there are chars left to parse or not
if (strlen(++inptPtr) > 0)
{
input = inptPtr;
}
else
{
input = NULL;
}
parsedWords[i] = (char *)malloc(MAX_NUM_OF_COMMAND_WORDS * sizeof(char));
cnt++;
strcpy(parsedWords[i], word);
if (input == NULL || strlen(input) == 0)
return cnt;
else
continue;
}
strcpy(word, strsep(&input, " "));
if (word == NULL)
break; // nothing to split
if (strlen(word) == 0) // read an empty command, re-iterate
{
i--;
continue;
}
parsedWords[i] = (char *)malloc(MAX_NUM_OF_COMMAND_WORDS * sizeof(char));
if (!parsedWords[i])
{
perror("Failed to allocate memory for command\n");
exit(EXIT_FAILURE);
}
cnt++;
strcpy(parsedWords[i], word);
if (input == NULL || strlen(input) == 0)
break;
}
return cnt;
}
/**
* Executes the given commands after parsing them according to
* their type (i.e. pipes, redirection, etc.).
* @param input A line read from the shell containing commands to execute
* */
void parseInput(char *input)
{
if (strchr(input, '|') != NULL)
{
// possibly piped command(s)
char *commands[MAX_NUM_OF_COMMANDS] = {
NULL,
};
int numOfCmds = splitAtPipe(input, commands);
execPipedCommands(input, commands);
int i;
for (i = 0; i < numOfCmds; i++)
if (commands[i] != NULL)
free(commands[i]);
}
else if (strchr(input, '>') != NULL || strchr(input, '<') != NULL)
{ // no need to check for >> since we check for >
// redirection commands
char *commands[MAX_NUM_OF_REDIR_CMDS] = {
NULL,
};
char *delim = (char *)malloc(3 * sizeof(char));
if (strstr(input, ">>"))
strcpy(delim, ">>");
else if (strchr(input, '>'))
strcpy(delim, ">");
else
strcpy(delim, "<");
splitAtRedirectionDelim(input, commands, delim);
execRedirectionCommands(input, commands, delim);
int i;
for (i = 0; i < MAX_NUM_OF_REDIR_CMDS; i++)
if (commands[i] != NULL)
free(commands[i]);
free(delim);
}
else if (strchr(input, ';') != NULL)
{
// possibly multiple command(s)
char *commands[MAX_NUM_OF_COMMANDS] = {
NULL,
};
int numOfCmds = parseMultipleCommands(input, commands);
int i;
for (i = 0; i < numOfCmds; i++)
{
if (commands[i] == NULL)
break;
// single command
char *args[MAX_NUM_OF_COMMAND_WORDS] = {
NULL,
};
int numOfArgs = parseCommandWords(commands[i], args);
if (handleCustomCommands(args,numOfArgs,input) == 0)
{
execSystemCommand(args);
}
int j;
for (j = 0; j < numOfArgs; j++)
{
free(args[j]);
}
if (commands[i] != NULL)
free(commands[i]);
}
}
else
{
// single command
char *args[MAX_NUM_OF_COMMAND_WORDS] = {
NULL,
};
int numOfArgs = parseCommandWords(input, args);
if (handleCustomCommands(args,numOfArgs,input) == 0)
{
execSystemCommand(args);
}
int i;
for (i = 0; i < numOfArgs; i++)
{
free(args[i]);
}
}
}
正如很多人指出的那样,我的代码还包含很多其他问题(mem 问题、处理引用 args 的逻辑有问题等),所以我将听取他们的意见,采取退后一步,在继续之前尝试分段测试所有内容。