C: Shell 程序接收到异常的额外操作数
C: Shell program receiving anomolous extra operands
我正在使用 fork
和 execvp
在 C 中创建自己的 shell。我正在使用 strtok
解析 cmd 及其参数。打印已解析的标记向我确认我确实收到了所有参数,并且 shell 通常有效,尽管它当然是非常基本的,因为我是一个菜鸟。然而,这里有两个典型的场景,当我 运行 和 shell 时,这让我感到困惑,请注意 ls
和 pwd
命令是如何第一次工作的,但随后开始得到这些来自某处的额外操作数:
➜ a2 ./shell
(user)># pwd
/home/user/ClionProjects/unix_programming/a2
(user)># ls -la
total 32
drwxrwxr-x 2 user user 4096 Mar 9 13:18 .
drwxrwxr-x 9 user user 4096 Mar 6 14:18 ..
-rwxrwxr-x 1 user user 13616 Mar 9 13:18 shell
-rw-rw-r-- 1 user user 3809 Mar 9 13:17 shell.c
-rw-rw-r-- 1 user user 545 Mar 9 12:58 shell.h
(user)># pwd
pwd: ignoring non-option arguments
/home/user/ClionProjects/unix_programming/a2
(user)>#
AND
➜ a2 ./shell
(user)># pwd
/home/user/ClionProjects/unix_programming/a2
(user)># ls -la
total 32
drwxrwxr-x 2 user user 4096 Mar 9 13:18 .
drwxrwxr-x 9 user user 4096 Mar 6 14:18 ..
-rwxrwxr-x 1 user user 13616 Mar 9 13:18 shell
-rw-rw-r-- 1 user user 3809 Mar 9 13:17 shell.c
-rw-rw-r-- 1 user user 545 Mar 9 12:58 shell.h
(user)># pwd
pwd: ignoring non-option arguments
/home/user/ClionProjects/unix_programming/a2
(user)>#
在下面的代码中,您可以看到我将参数存储在 tokens
中。第一个标记是 cmd 文件名本身。
我已经尝试 memset
将 tokens
设置为全零,free
设置它并在 while 循环的每次迭代开始时重新设置它 malloc
.我一直尝试将 tokens
的大小调整为当前其中的 args 数量。我做了这些事情是因为我确信 tokens
以某种方式保留了先前命令的内容,这些内容出于某种原因被以后的命令读取。但是重新malloc
ing和realloc
ing tokens
都没有触及问题,所以我现在很茫然。非常感谢任何正确方向的建议或推动。
shell.h
#ifndef UNIX_PROGRAMMING_SHELL_H
#define UNIX_PROGRAMMING_SHELL_H
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
int sig_int = 0;
void signal_handler(int sig_num);
void start_shell();
void prompt();
void change_dir(char *path, pid_t *pid, int *status);
void execute_cmd(const char *file_name, char *const *args);
void parse_cmd(char *cmd, char **tokens, size_t *index);
#endif //UNIX_PROGRAMMING_SHELL_H
shell.c
#include "shell.h"
void signal_handler(int sig_num) {
printf("\n"); // Do nothing when Ctrl-C is pressed
}
void prompt() {
/* Get current users username and display prompt */
char *user = getlogin();
printf("(%s)># ", (user==NULL)? "": user);
}
void change_dir(char *path, pid_t *pid, int *status) {
*pid = fork();
if (*pid == -1) {
printf("Error changing directory..\n");
} else if (*pid == 0) {
chdir(path);
} else {
*pid = wait(status);
if (*pid == -1) {
printf("%s\n", strerror(errno));
return;
}
}
}
void execute_cmd(const char *file_name, char *const *args) {
execvp(file_name, args);
}
/** parse commands into tokens... **/
void parse_cmd(char *cmd, char **tokens, size_t *index) {
char *tok;
const char *delim = " ";
*index = 0;
// TODO: realloc tokens, so it can start from 2 and build up as needed
tok = strtok(cmd, delim);
if (tok != NULL) {
tokens[*index] = tok;
(*index)++;
} else {
tokens[*index] = "[=14=]";
return;
}
while ((tok = strtok(NULL, delim)) != NULL) {
tokens[*index] = tok;
(*index)++;
}
// for (size_t i = 0; i < *index; i++) {
// printf("arg[%zu]: %s\n", i, tokens[i]);
// }
printf("\n");
}
void start_shell() {
ssize_t c;
size_t cmd_size = 20;
size_t num_args = 5;
int *status = NULL;
pid_t pid;
char *cmd = (char *) malloc(sizeof(char)); /* command line input */
char **tokens = (char **) malloc(sizeof(char *) * num_args); /* command line input parsed into tokens */
size_t *index = (size_t *) malloc(sizeof(size_t)); /* number of tokens parsed */
if (tokens == NULL) {
printf("Error: Out of memory..");
exit(EXIT_FAILURE);
}
prompt();
/* main loop - get input, parse, process - until termination */
while ( (c = getline(&cmd, &cmd_size, stdin)) != EOF ) {
cmd[strcspn(cmd, "\n")] = '[=14=]'; /* trim newline */
parse_cmd(cmd, tokens, index);
/* resize tokens to fit only it's current contents */
// if (*index < num_args && *index != 0 && *index != 1) {
tokens = realloc(tokens, *index);
// }
const char *file_name = tokens[0];
char *const *args = tokens;
/* If command is blank */
if ( (c = strcspn(file_name, "\n\r[=14=]") == 0) ) {
tokens[0] = "[=14=]";
prompt();
continue;
} else if ( (c = strcmp(file_name, "exit")) == 0 ) {
break;
} else if ( (c = strcmp(file_name, "cd")) == 0 ) {
if (*index == 1) { /* no path provided */
chdir(getenv("HOME"));
} else {
char *path = realpath(args[1], NULL);
if (path == NULL) {
printf("%s\n", strerror(errno));
break;
} else {
change_dir(path, &pid, status);
}
}
}
// fork here ... success: parent < pid .. child << 0 -- failure: parent << -1
pid = fork();
if (pid == -1) {
printf("Error executing command\n");
continue;
} else if (pid == 0) {
execute_cmd(file_name, args);
} else {
pid = wait(status);
if (pid == -1) {
printf("%s\n", strerror(errno));
exit(EXIT_FAILURE);
}
}
tokens[0] = "[=14=]";
prompt();
}
free(cmd);
free(tokens);
free(index);
printf("\n"); /* avoids unwanted terminal output for ctrl-D */
}
int main(void) {
signal(SIGINT, signal_handler);
start_shell();
return 0;
}
execvp
需要参数列表被 NULL
终止(否则它不能告诉参数计数)
您在构建命令时没有添加 NULL
。所以这是未定义的行为,一个或两个额外的幽灵参数被传递给 execvp
(直到它最终找到一个 NULL
或者只是一个很好的旧 segfault)
所以:
char **tokens = (char **) malloc(sizeof(char *) * num_args); /* command line input parsed into tokens */
应该是
char **tokens = malloc(sizeof(char *) * (num_args + 1)); /* command line input parsed into tokens */
并在 parse_cmd
中添加 NULL
项:
while ((tok = strtok(NULL, delim)) != NULL) {
tokens[*index] = tok;
(*index)++;
} // this is your code, now
tokens[*index] = NULL;
(*index)++; // maybe not necessary
现在 execvp
传递了一个 NULL
终止字符串。
我正在使用 fork
和 execvp
在 C 中创建自己的 shell。我正在使用 strtok
解析 cmd 及其参数。打印已解析的标记向我确认我确实收到了所有参数,并且 shell 通常有效,尽管它当然是非常基本的,因为我是一个菜鸟。然而,这里有两个典型的场景,当我 运行 和 shell 时,这让我感到困惑,请注意 ls
和 pwd
命令是如何第一次工作的,但随后开始得到这些来自某处的额外操作数:
➜ a2 ./shell
(user)># pwd
/home/user/ClionProjects/unix_programming/a2
(user)># ls -la
total 32
drwxrwxr-x 2 user user 4096 Mar 9 13:18 .
drwxrwxr-x 9 user user 4096 Mar 6 14:18 ..
-rwxrwxr-x 1 user user 13616 Mar 9 13:18 shell
-rw-rw-r-- 1 user user 3809 Mar 9 13:17 shell.c
-rw-rw-r-- 1 user user 545 Mar 9 12:58 shell.h
(user)># pwd
pwd: ignoring non-option arguments
/home/user/ClionProjects/unix_programming/a2
(user)>#
AND
➜ a2 ./shell
(user)># pwd
/home/user/ClionProjects/unix_programming/a2
(user)># ls -la
total 32
drwxrwxr-x 2 user user 4096 Mar 9 13:18 .
drwxrwxr-x 9 user user 4096 Mar 6 14:18 ..
-rwxrwxr-x 1 user user 13616 Mar 9 13:18 shell
-rw-rw-r-- 1 user user 3809 Mar 9 13:17 shell.c
-rw-rw-r-- 1 user user 545 Mar 9 12:58 shell.h
(user)># pwd
pwd: ignoring non-option arguments
/home/user/ClionProjects/unix_programming/a2
(user)>#
在下面的代码中,您可以看到我将参数存储在 tokens
中。第一个标记是 cmd 文件名本身。
我已经尝试 memset
将 tokens
设置为全零,free
设置它并在 while 循环的每次迭代开始时重新设置它 malloc
.我一直尝试将 tokens
的大小调整为当前其中的 args 数量。我做了这些事情是因为我确信 tokens
以某种方式保留了先前命令的内容,这些内容出于某种原因被以后的命令读取。但是重新malloc
ing和realloc
ing tokens
都没有触及问题,所以我现在很茫然。非常感谢任何正确方向的建议或推动。
shell.h
#ifndef UNIX_PROGRAMMING_SHELL_H
#define UNIX_PROGRAMMING_SHELL_H
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
int sig_int = 0;
void signal_handler(int sig_num);
void start_shell();
void prompt();
void change_dir(char *path, pid_t *pid, int *status);
void execute_cmd(const char *file_name, char *const *args);
void parse_cmd(char *cmd, char **tokens, size_t *index);
#endif //UNIX_PROGRAMMING_SHELL_H
shell.c
#include "shell.h"
void signal_handler(int sig_num) {
printf("\n"); // Do nothing when Ctrl-C is pressed
}
void prompt() {
/* Get current users username and display prompt */
char *user = getlogin();
printf("(%s)># ", (user==NULL)? "": user);
}
void change_dir(char *path, pid_t *pid, int *status) {
*pid = fork();
if (*pid == -1) {
printf("Error changing directory..\n");
} else if (*pid == 0) {
chdir(path);
} else {
*pid = wait(status);
if (*pid == -1) {
printf("%s\n", strerror(errno));
return;
}
}
}
void execute_cmd(const char *file_name, char *const *args) {
execvp(file_name, args);
}
/** parse commands into tokens... **/
void parse_cmd(char *cmd, char **tokens, size_t *index) {
char *tok;
const char *delim = " ";
*index = 0;
// TODO: realloc tokens, so it can start from 2 and build up as needed
tok = strtok(cmd, delim);
if (tok != NULL) {
tokens[*index] = tok;
(*index)++;
} else {
tokens[*index] = "[=14=]";
return;
}
while ((tok = strtok(NULL, delim)) != NULL) {
tokens[*index] = tok;
(*index)++;
}
// for (size_t i = 0; i < *index; i++) {
// printf("arg[%zu]: %s\n", i, tokens[i]);
// }
printf("\n");
}
void start_shell() {
ssize_t c;
size_t cmd_size = 20;
size_t num_args = 5;
int *status = NULL;
pid_t pid;
char *cmd = (char *) malloc(sizeof(char)); /* command line input */
char **tokens = (char **) malloc(sizeof(char *) * num_args); /* command line input parsed into tokens */
size_t *index = (size_t *) malloc(sizeof(size_t)); /* number of tokens parsed */
if (tokens == NULL) {
printf("Error: Out of memory..");
exit(EXIT_FAILURE);
}
prompt();
/* main loop - get input, parse, process - until termination */
while ( (c = getline(&cmd, &cmd_size, stdin)) != EOF ) {
cmd[strcspn(cmd, "\n")] = '[=14=]'; /* trim newline */
parse_cmd(cmd, tokens, index);
/* resize tokens to fit only it's current contents */
// if (*index < num_args && *index != 0 && *index != 1) {
tokens = realloc(tokens, *index);
// }
const char *file_name = tokens[0];
char *const *args = tokens;
/* If command is blank */
if ( (c = strcspn(file_name, "\n\r[=14=]") == 0) ) {
tokens[0] = "[=14=]";
prompt();
continue;
} else if ( (c = strcmp(file_name, "exit")) == 0 ) {
break;
} else if ( (c = strcmp(file_name, "cd")) == 0 ) {
if (*index == 1) { /* no path provided */
chdir(getenv("HOME"));
} else {
char *path = realpath(args[1], NULL);
if (path == NULL) {
printf("%s\n", strerror(errno));
break;
} else {
change_dir(path, &pid, status);
}
}
}
// fork here ... success: parent < pid .. child << 0 -- failure: parent << -1
pid = fork();
if (pid == -1) {
printf("Error executing command\n");
continue;
} else if (pid == 0) {
execute_cmd(file_name, args);
} else {
pid = wait(status);
if (pid == -1) {
printf("%s\n", strerror(errno));
exit(EXIT_FAILURE);
}
}
tokens[0] = "[=14=]";
prompt();
}
free(cmd);
free(tokens);
free(index);
printf("\n"); /* avoids unwanted terminal output for ctrl-D */
}
int main(void) {
signal(SIGINT, signal_handler);
start_shell();
return 0;
}
execvp
需要参数列表被 NULL
终止(否则它不能告诉参数计数)
您在构建命令时没有添加 NULL
。所以这是未定义的行为,一个或两个额外的幽灵参数被传递给 execvp
(直到它最终找到一个 NULL
或者只是一个很好的旧 segfault)
所以:
char **tokens = (char **) malloc(sizeof(char *) * num_args); /* command line input parsed into tokens */
应该是
char **tokens = malloc(sizeof(char *) * (num_args + 1)); /* command line input parsed into tokens */
并在 parse_cmd
中添加 NULL
项:
while ((tok = strtok(NULL, delim)) != NULL) {
tokens[*index] = tok;
(*index)++;
} // this is your code, now
tokens[*index] = NULL;
(*index)++; // maybe not necessary
现在 execvp
传递了一个 NULL
终止字符串。